nodebb-plugin-ezoic-infinite 0.9.8 → 0.9.9
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 -38
- package/package.json +5 -2
- package/plugin.json +2 -5
- package/public/admin.js +25 -4
- package/public/client.js +212 -320
- package/public/templates/admin/plugins/ezoic-infinite.tpl +35 -35
- package/public/style.css +0 -5
package/library.js
CHANGED
|
@@ -1,57 +1,96 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const meta = require.main.require('./src/meta');
|
|
4
|
-
const
|
|
4
|
+
const db = require.main.require('./src/database');
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const SETTINGS_KEY = 'ezoic-infinite';
|
|
7
|
+
const plugin = {};
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
function normalizeExcludedGroups(value) {
|
|
10
|
+
if (!value) return [];
|
|
11
|
+
if (Array.isArray(value)) return value;
|
|
12
|
+
return String(value).split(',').map(s => s.trim()).filter(Boolean);
|
|
13
|
+
}
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
function parseBool(v, def = false) {
|
|
16
|
+
if (v === undefined || v === null || v === '') return def;
|
|
17
|
+
if (typeof v === 'boolean') return v;
|
|
18
|
+
const s = String(v).toLowerCase();
|
|
19
|
+
return s === '1' || s === 'true' || s === 'on' || s === 'yes';
|
|
20
|
+
}
|
|
14
21
|
|
|
15
|
-
async function
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
async function getAllGroups() {
|
|
23
|
+
const names = await db.getSortedSetRange('groups:createtime', 0, -1);
|
|
24
|
+
const filtered = names.filter(name => !groups.isPrivilegeGroup(name));
|
|
25
|
+
const data = await groups.getGroupsData(filtered);
|
|
26
|
+
// Sort alphabetically for ACP usability
|
|
27
|
+
data.sort((a, b) => String(a.name).localeCompare(String(b.name), 'fr', { sensitivity: 'base' }));
|
|
28
|
+
return data;
|
|
29
|
+
}
|
|
30
|
+
async function getSettings() {
|
|
31
|
+
const s = await meta.settings.get(SETTINGS_KEY);
|
|
32
|
+
return {
|
|
33
|
+
// Between-post ads (simple blocks)
|
|
34
|
+
enableBetweenAds: parseBool(s.enableBetweenAds, true),
|
|
35
|
+
placeholderIds: (s.placeholderIds || '').trim(),
|
|
36
|
+
intervalPosts: Math.max(1, parseInt(s.intervalPosts, 10) || 6),
|
|
28
37
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
groupList = groupNames.map((name) => ({ name }));
|
|
34
|
-
}
|
|
38
|
+
// "Ad message" between replies (looks like a post)
|
|
39
|
+
enableMessageAds: parseBool(s.enableMessageAds, false),
|
|
40
|
+
messagePlaceholderIds: (s.messagePlaceholderIds || '').trim(),
|
|
41
|
+
messageIntervalPosts: Math.max(1, parseInt(s.messageIntervalPosts, 10) || 3),
|
|
35
42
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
excludedGroups: normalizeExcludedGroups(s.excludedGroups),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
39
46
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
});
|
|
47
|
+
async function isUserExcluded(uid, excludedGroups) {
|
|
48
|
+
if (!uid || !excludedGroups.length) return false;
|
|
49
|
+
const userGroups = await groups.getUserGroups([uid]);
|
|
50
|
+
return (userGroups[0] || []).some(g => excludedGroups.includes(g.name));
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
|
|
53
|
+
plugin.addAdminNavigation = async (header) => {
|
|
48
54
|
header.plugins = header.plugins || [];
|
|
49
55
|
header.plugins.push({
|
|
50
56
|
route: '/plugins/ezoic-infinite',
|
|
51
|
-
icon: 'fa-
|
|
52
|
-
name: 'Ezoic Infinite'
|
|
57
|
+
icon: 'fa-ad',
|
|
58
|
+
name: 'Ezoic Infinite Ads'
|
|
53
59
|
});
|
|
54
60
|
return header;
|
|
55
61
|
};
|
|
56
62
|
|
|
57
|
-
|
|
63
|
+
plugin.init = async ({ router, middleware }) => {
|
|
64
|
+
async function render(req, res) {
|
|
65
|
+
const settings = await getSettings();
|
|
66
|
+
const allGroups = await getAllGroups();
|
|
67
|
+
|
|
68
|
+
res.render('admin/plugins/ezoic-infinite', {
|
|
69
|
+
title: 'Ezoic Infinite Ads',
|
|
70
|
+
...settings,
|
|
71
|
+
enableBetweenAds_checked: settings.enableBetweenAds ? 'checked' : '',
|
|
72
|
+
enableMessageAds_checked: settings.enableMessageAds ? 'checked' : '',
|
|
73
|
+
allGroups,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
router.get('/admin/plugins/ezoic-infinite', middleware.admin.buildHeader, render);
|
|
78
|
+
router.get('/api/admin/plugins/ezoic-infinite', render);
|
|
79
|
+
|
|
80
|
+
router.get('/api/plugins/ezoic-infinite/config', middleware.buildHeader, async (req, res) => {
|
|
81
|
+
const settings = await getSettings();
|
|
82
|
+
const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
|
|
83
|
+
|
|
84
|
+
res.json({
|
|
85
|
+
excluded,
|
|
86
|
+
enableBetweenAds: settings.enableBetweenAds,
|
|
87
|
+
placeholderIds: settings.placeholderIds,
|
|
88
|
+
intervalPosts: settings.intervalPosts,
|
|
89
|
+
enableMessageAds: settings.enableMessageAds,
|
|
90
|
+
messagePlaceholderIds: settings.messagePlaceholderIds,
|
|
91
|
+
messageIntervalPosts: settings.messageIntervalPosts,
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
module.exports = plugin;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-ezoic-infinite",
|
|
3
|
-
"version": "0.9.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.9.9",
|
|
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
|
{
|
|
@@ -22,8 +22,5 @@
|
|
|
22
22
|
"scripts": [
|
|
23
23
|
"public/client.js"
|
|
24
24
|
],
|
|
25
|
-
"css": [
|
|
26
|
-
"public/style.css"
|
|
27
|
-
],
|
|
28
25
|
"templates": "public/templates"
|
|
29
26
|
}
|
package/public/admin.js
CHANGED
|
@@ -19,9 +19,25 @@ $(document).ready(function () {
|
|
|
19
19
|
form.find('[name="messageIntervalPosts"]').val(parseInt(data.messageIntervalPosts, 10) || 3);
|
|
20
20
|
form.find('[name="messagePlaceholderIds"]').val(data.messagePlaceholderIds || '');
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
$
|
|
22
|
+
// NodeBB 4.x: charger les groupes via socket côté ACP
|
|
23
|
+
socket.emit('groups.getGroups', {}, function (err2, groups) {
|
|
24
|
+
const $select = form.find('[name="excludedGroups"]');
|
|
25
|
+
$select.empty();
|
|
26
|
+
|
|
27
|
+
if (!err2 && Array.isArray(groups)) {
|
|
28
|
+
groups
|
|
29
|
+
.map(g => g && g.name)
|
|
30
|
+
.filter(Boolean)
|
|
31
|
+
.sort((a, b) => a.localeCompare(b, 'fr', { sensitivity: 'base' }))
|
|
32
|
+
.forEach((name) => {
|
|
33
|
+
$select.append($('<option>').val(name).text(name));
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const selected = (data.excludedGroups || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
38
|
+
$select.find('option').each(function () {
|
|
39
|
+
$(this).prop('selected', selected.includes($(this).val()));
|
|
40
|
+
});
|
|
25
41
|
});
|
|
26
42
|
});
|
|
27
43
|
}
|
|
@@ -49,6 +65,11 @@ $(document).ready(function () {
|
|
|
49
65
|
});
|
|
50
66
|
}
|
|
51
67
|
|
|
52
|
-
$(
|
|
68
|
+
$(document).off('click.ezoicInfiniteSave', '.ezoic-infinite-save')
|
|
69
|
+
.on('click.ezoicInfiniteSave', '.ezoic-infinite-save', function (e) {
|
|
70
|
+
e.preventDefault();
|
|
71
|
+
save();
|
|
72
|
+
});
|
|
73
|
+
|
|
53
74
|
load();
|
|
54
75
|
});
|
package/public/client.js
CHANGED
|
@@ -1,360 +1,252 @@
|
|
|
1
|
-
/* global $, ajaxify, app, socket */
|
|
2
1
|
'use strict';
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
.
|
|
29
|
-
|
|
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) {
|
|
41
|
-
return false;
|
|
3
|
+
/* globals ajaxify */
|
|
4
|
+
window.ezoicInfiniteLoaded = true;
|
|
5
|
+
|
|
6
|
+
let cachedConfig;
|
|
7
|
+
let lastFetch = 0;
|
|
8
|
+
let debounceTimer;
|
|
9
|
+
|
|
10
|
+
let inFlight = false;
|
|
11
|
+
let rerunRequested = false;
|
|
12
|
+
|
|
13
|
+
// Incremental state (prevents ads "jumping to the top")
|
|
14
|
+
let pageKey = null;
|
|
15
|
+
let injectedSlots = new Set(); // slotNumber per page
|
|
16
|
+
let usedIds = new Set(); // ids currently injected per page
|
|
17
|
+
|
|
18
|
+
function resetPageState() {
|
|
19
|
+
injectedSlots = new Set();
|
|
20
|
+
usedIds = new Set();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function currentPageKey() {
|
|
24
|
+
// Stable key per ajaxified page
|
|
25
|
+
try {
|
|
26
|
+
if (ajaxify && ajaxify.data) {
|
|
27
|
+
if (ajaxify.data.tid) return 'topic:' + ajaxify.data.tid;
|
|
28
|
+
if (ajaxify.data.cid) return 'category:' + ajaxify.data.cid;
|
|
29
|
+
if (ajaxify.data.template) return 'tpl:' + ajaxify.data.template + ':' + (ajaxify.data.url || window.location.pathname);
|
|
42
30
|
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
31
|
+
} catch (e) {}
|
|
32
|
+
return window.location.pathname;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parsePool(raw) {
|
|
36
|
+
if (!raw) return [];
|
|
37
|
+
return Array.from(new Set(
|
|
38
|
+
String(raw).split(/[\n,;\s]+/)
|
|
39
|
+
.map(x => parseInt(x, 10))
|
|
40
|
+
.filter(n => Number.isFinite(n) && n > 0)
|
|
41
|
+
));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function fetchConfig() {
|
|
45
|
+
if (cachedConfig && Date.now() - lastFetch < 10000) return cachedConfig;
|
|
46
|
+
const res = await fetch('/api/plugins/ezoic-infinite/config', { credentials: 'same-origin' });
|
|
47
|
+
cachedConfig = await res.json();
|
|
48
|
+
lastFetch = Date.now();
|
|
49
|
+
return cachedConfig;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isTopicPage() {
|
|
53
|
+
return $('[component="post/content"]').length > 0 || $('[component="post"][data-pid]').length > 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isCategoryTopicListPage() {
|
|
57
|
+
return $('li[component="category/topic"]').length > 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getTopicPosts() {
|
|
61
|
+
const $primary = $('[component="post"][data-pid]');
|
|
62
|
+
if ($primary.length) return $primary.not('.ezoic-ad-post');
|
|
63
|
+
|
|
64
|
+
return $('[data-pid]').filter(function () {
|
|
65
|
+
const $el = $(this);
|
|
66
|
+
const hasContent = $el.find('[component="post/content"]').length > 0;
|
|
67
|
+
const nested = $el.parents('[data-pid]').length > 0;
|
|
68
|
+
return hasContent && !nested;
|
|
69
|
+
}).not('.ezoic-ad-post');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getCategoryTopicItems() {
|
|
73
|
+
return $('li[component="category/topic"]').not('.ezoic-ad-topic');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function tagName($el) {
|
|
77
|
+
return ($el && $el.length ? (($el.prop('tagName') || '').toUpperCase()) : '');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function makeWrapperLike($target, classes, innerHtml, attrs) {
|
|
81
|
+
const t = tagName($target);
|
|
82
|
+
const attrStr = attrs ? ' ' + attrs : '';
|
|
83
|
+
if (t === 'LI') {
|
|
84
|
+
return '<li class="' + classes + ' list-unstyled"' + attrStr + '>' + innerHtml + '</li>';
|
|
85
|
+
}
|
|
86
|
+
return '<div class="' + classes + '"' + attrStr + '>' + innerHtml + '</div>';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function pickNextId(pool) {
|
|
90
|
+
for (const id of pool) {
|
|
91
|
+
if (!usedIds.has(id)) return id;
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function callEzoic(ids) {
|
|
97
|
+
if (!ids || !ids.length) return;
|
|
98
|
+
|
|
99
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
100
|
+
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
101
|
+
|
|
102
|
+
const run = function () {
|
|
46
103
|
try {
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
104
|
+
if (typeof window.ezstandalone.showAds === 'function') {
|
|
105
|
+
window.ezstandalone.showAds.apply(window.ezstandalone, ids);
|
|
106
|
+
return true;
|
|
50
107
|
}
|
|
51
108
|
} catch (e) {}
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
109
|
+
return false;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
window.ezstandalone.cmd.push(function () { run(); });
|
|
113
|
+
|
|
114
|
+
// retry a few times (Ezoic can load late)
|
|
115
|
+
let tries = 0;
|
|
116
|
+
const maxTries = 6;
|
|
117
|
+
const timer = setInterval(function () {
|
|
118
|
+
tries++;
|
|
119
|
+
if (run() || tries >= maxTries) clearInterval(timer);
|
|
120
|
+
}, 800);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function injectBetweenIncremental($items, pool, interval, wrapperClass) {
|
|
124
|
+
const total = $items.length;
|
|
125
|
+
const maxSlot = Math.floor(total / interval);
|
|
126
|
+
if (maxSlot <= 0) return [];
|
|
127
|
+
|
|
128
|
+
const newIds = [];
|
|
129
|
+
|
|
130
|
+
for (let slot = 1; slot <= maxSlot; slot++) {
|
|
131
|
+
if (injectedSlots.has(slot)) continue;
|
|
132
|
+
|
|
133
|
+
const index = slot * interval - 1;
|
|
134
|
+
const $target = $items.eq(index);
|
|
135
|
+
if (!$target.length) continue;
|
|
136
|
+
|
|
137
|
+
const id = pickNextId(pool);
|
|
138
|
+
if (!id) {
|
|
139
|
+
// pool exhausted: stop injecting further to avoid reusing ids and "jumping"
|
|
140
|
+
break;
|
|
75
141
|
}
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
142
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
82
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
83
|
-
if (typeof window.ezstandalone.destroyPlaceholders === 'function') {
|
|
84
|
-
window.ezstandalone.destroyPlaceholders(id);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
window.ezstandalone.cmd.push(function () {
|
|
88
|
-
try { window.ezstandalone.destroyPlaceholders(id); } catch (e) {}
|
|
89
|
-
});
|
|
90
|
-
} catch (e) {}
|
|
91
|
-
}
|
|
143
|
+
const placeholder = '<div id="ezoic-pub-ad-placeholder-' + id + '"></div>';
|
|
144
|
+
const html = makeWrapperLike($target, wrapperClass, placeholder, 'data-ezoic-slot="' + slot + '" data-ezoic-id="' + id + '"');
|
|
92
145
|
|
|
93
|
-
|
|
94
|
-
const existing = document.getElementById('ezoic-pub-ad-placeholder-' + id);
|
|
95
|
-
if (!existing) return;
|
|
146
|
+
$target.after(html);
|
|
96
147
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
} else {
|
|
101
|
-
existing.remove();
|
|
102
|
-
}
|
|
103
|
-
// On détruit le slot correspondant pour éviter les comportements imprévisibles
|
|
104
|
-
destroyPlaceholder(id);
|
|
148
|
+
injectedSlots.add(slot);
|
|
149
|
+
usedIds.add(id);
|
|
150
|
+
newIds.push(id);
|
|
105
151
|
}
|
|
106
152
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (!ids || !ids.length) return;
|
|
153
|
+
return newIds;
|
|
154
|
+
}
|
|
110
155
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
window.__ezoicLastShowKey = key;
|
|
116
|
-
window.__ezoicLastShowAt = now;
|
|
156
|
+
function injectMessageIncremental($posts, pool, interval) {
|
|
157
|
+
const total = $posts.length;
|
|
158
|
+
const maxSlot = Math.floor(total / interval);
|
|
159
|
+
if (maxSlot <= 0) return [];
|
|
117
160
|
|
|
118
|
-
|
|
119
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
120
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
121
|
-
|
|
122
|
-
const run = function () {
|
|
123
|
-
try {
|
|
124
|
-
if (typeof window.ezstandalone.showAds === 'function') {
|
|
125
|
-
// Ezoic accepte plusieurs args: showAds(1,2,3)
|
|
126
|
-
window.ezstandalone.showAds.apply(window.ezstandalone, ids);
|
|
127
|
-
return true;
|
|
128
|
-
}
|
|
129
|
-
} catch (e) {}
|
|
130
|
-
return false;
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
window.ezstandalone.cmd.push(function () { run(); });
|
|
134
|
-
|
|
135
|
-
// Retry a few times if ez loads late
|
|
136
|
-
let tries = 0;
|
|
137
|
-
const tick = function () {
|
|
138
|
-
tries++;
|
|
139
|
-
if (run() || tries >= 8) return;
|
|
140
|
-
setTimeout(tick, 800);
|
|
141
|
-
};
|
|
142
|
-
setTimeout(tick, 800);
|
|
143
|
-
} catch (e) {}
|
|
144
|
-
}
|
|
161
|
+
const newIds = [];
|
|
145
162
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (window.__ezoicAutoHeight) return;
|
|
149
|
-
window.__ezoicAutoHeight = true;
|
|
163
|
+
for (let slot = 1; slot <= maxSlot; slot++) {
|
|
164
|
+
if (injectedSlots.has(slot)) continue;
|
|
150
165
|
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (ph && ph.children && ph.children.length) {
|
|
155
|
-
wrap.classList.add('ezoic-filled');
|
|
156
|
-
}
|
|
157
|
-
};
|
|
166
|
+
const index = slot * interval - 1;
|
|
167
|
+
const $target = $posts.eq(index);
|
|
168
|
+
if (!$target.length) continue;
|
|
158
169
|
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
};
|
|
170
|
+
const id = pickNextId(pool);
|
|
171
|
+
if (!id) break;
|
|
162
172
|
|
|
163
|
-
|
|
164
|
-
|
|
173
|
+
const inner = '<div class="content"><div id="ezoic-pub-ad-placeholder-' + id + '"></div></div>';
|
|
174
|
+
const html = makeWrapperLike($target, 'post ezoic-ad-post', inner, 'data-ezoic-slot="' + slot + '" data-ezoic-id="' + id + '"');
|
|
165
175
|
|
|
166
|
-
|
|
167
|
-
const mo = new MutationObserver(scan);
|
|
168
|
-
mo.observe(document.body, { childList: true, subtree: true });
|
|
169
|
-
} catch (e) {}
|
|
170
|
-
}
|
|
176
|
+
$target.after(html);
|
|
171
177
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
'<div class="ezoic-ad ' + cls + '" data-ezoic-id="' + id + '" data-ezoic-after="' + afterVal + '">' +
|
|
176
|
-
'<div class="ezoic-ad-inner"><div id="ezoic-pub-ad-placeholder-' + id + '"></div></div>' +
|
|
177
|
-
'</div>'
|
|
178
|
-
);
|
|
179
|
-
$target.after(wrap);
|
|
180
|
-
return wrap;
|
|
178
|
+
injectedSlots.add(slot);
|
|
179
|
+
usedIds.add(id);
|
|
180
|
+
newIds.push(id);
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
while (fifoTopic.length) {
|
|
186
|
-
const old = fifoTopic.shift();
|
|
187
|
-
const sel = '.ezoic-ad-topic[data-ezoic-id="' + old.id + '"][data-ezoic-after="' + old.afterNo + '"]';
|
|
188
|
-
const $el = $(sel);
|
|
189
|
-
if (!$el.length) continue;
|
|
190
|
-
|
|
191
|
-
// Ne pas recycler si collé juste avant le dernier post (sentinel / scroll)
|
|
192
|
-
try {
|
|
193
|
-
const $last = $posts.last();
|
|
194
|
-
if ($last.length && $el.prev().is($last)) {
|
|
195
|
-
fifoTopic.push(old);
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
} catch (e) {}
|
|
199
|
-
|
|
200
|
-
$el.remove();
|
|
201
|
-
usedTopic.delete(old.id);
|
|
202
|
-
destroyPlaceholder(old.id);
|
|
203
|
-
return old.id;
|
|
204
|
-
}
|
|
205
|
-
return null;
|
|
206
|
-
}
|
|
183
|
+
return newIds;
|
|
184
|
+
}
|
|
207
185
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
try {
|
|
217
|
-
const $last = $items.last();
|
|
218
|
-
if ($last.length && $el.prev().is($last)) {
|
|
219
|
-
fifoCat.push(old);
|
|
220
|
-
return null;
|
|
221
|
-
}
|
|
222
|
-
} catch (e) {}
|
|
223
|
-
|
|
224
|
-
$el.remove();
|
|
225
|
-
usedCat.delete(old.id);
|
|
226
|
-
destroyPlaceholder(old.id);
|
|
227
|
-
return old.id;
|
|
228
|
-
}
|
|
229
|
-
return null;
|
|
186
|
+
async function refreshAds() {
|
|
187
|
+
// reset state when navigating (ajaxify)
|
|
188
|
+
const key = currentPageKey();
|
|
189
|
+
if (pageKey !== key) {
|
|
190
|
+
pageKey = key;
|
|
191
|
+
resetPageState();
|
|
192
|
+
// also cleanup any injected wrappers that may have been left by browser bfcache
|
|
193
|
+
$('.ezoic-ad-post, .ezoic-ad-between, .ezoic-ad-topic').remove();
|
|
230
194
|
}
|
|
231
195
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const interval = parseInt(settings.messageIntervalPosts, 10) || 3;
|
|
236
|
-
const pool = parsePool(settings.messagePlaceholderIds);
|
|
237
|
-
if (!pool.length) return;
|
|
238
|
-
|
|
239
|
-
const $posts = $('[component="post"][data-pid]');
|
|
240
|
-
if (!$posts.length) return;
|
|
196
|
+
if (inFlight) { rerunRequested = true; return; }
|
|
197
|
+
inFlight = true;
|
|
241
198
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
if (postNo % interval !== 0) return;
|
|
246
|
-
if (idx === $posts.length - 1) return; // pas après le dernier
|
|
247
|
-
|
|
248
|
-
const $post = $(this);
|
|
249
|
-
const existing = $post.next('.ezoic-ad-topic');
|
|
250
|
-
if (existing.length) return;
|
|
251
|
-
|
|
252
|
-
let id = pickNextId(pool, usedTopic);
|
|
253
|
-
if (!id) {
|
|
254
|
-
id = recycleTopic($posts);
|
|
255
|
-
if (!id) return;
|
|
256
|
-
}
|
|
199
|
+
try {
|
|
200
|
+
const cfg = await fetchConfig();
|
|
201
|
+
if (!cfg || cfg.excluded) return;
|
|
257
202
|
|
|
258
|
-
|
|
259
|
-
|
|
203
|
+
const betweenPool = parsePool(cfg.placeholderIds);
|
|
204
|
+
const betweenInterval = Math.max(1, parseInt(cfg.intervalPosts, 10) || 6);
|
|
260
205
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
if (newIds.length) callEzoic(newIds);
|
|
266
|
-
}
|
|
206
|
+
const messagePool = parsePool(cfg.messagePlaceholderIds);
|
|
207
|
+
const messageInterval = Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3);
|
|
267
208
|
|
|
268
|
-
|
|
269
|
-
|
|
209
|
+
const onTopic = isTopicPage();
|
|
210
|
+
const onCategory = !onTopic && isCategoryTopicListPage();
|
|
270
211
|
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
if (!pool.length) return;
|
|
212
|
+
const $posts = onTopic ? getTopicPosts() : $();
|
|
213
|
+
const $topicItems = onCategory ? getCategoryTopicItems() : $();
|
|
274
214
|
|
|
275
|
-
|
|
276
|
-
if (!$items.length) return;
|
|
215
|
+
if (!$posts.length && !$topicItems.length) return;
|
|
277
216
|
|
|
278
217
|
const newIds = [];
|
|
279
|
-
$items.each(function (idx) {
|
|
280
|
-
const pos = idx + 1;
|
|
281
|
-
if (pos % interval !== 0) return;
|
|
282
|
-
if (idx === $items.length - 1) return;
|
|
283
|
-
|
|
284
|
-
const $li = $(this);
|
|
285
|
-
const existing = $li.next('.ezoic-ad-between');
|
|
286
|
-
if (existing.length) return;
|
|
287
|
-
|
|
288
|
-
let id = pickNextId(pool, usedCat);
|
|
289
|
-
if (!id) {
|
|
290
|
-
id = recycleCat($items);
|
|
291
|
-
if (!id) return;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
usedCat.add(id);
|
|
295
|
-
fifoCat.push({ id, afterPos: pos });
|
|
296
|
-
|
|
297
|
-
insertAfter($li, id, 'between', pos, 'ezoic-ad-between');
|
|
298
|
-
newIds.push(id);
|
|
299
|
-
});
|
|
300
218
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
if (refreshInFlight) { refreshQueued = true; return; }
|
|
309
|
-
refreshInFlight = true;
|
|
310
|
-
try {
|
|
311
|
-
const key = getPageKey();
|
|
312
|
-
if (pageKey !== key) {
|
|
313
|
-
pageKey = key;
|
|
314
|
-
cleanupForNewPage();
|
|
219
|
+
// Your rule:
|
|
220
|
+
// - Category topic list: BETWEEN only
|
|
221
|
+
// - Topic page: MESSAGE only
|
|
222
|
+
if ($topicItems.length) {
|
|
223
|
+
if (cfg.enableBetweenAds && betweenPool.length) {
|
|
224
|
+
newIds.push(...injectBetweenIncremental($topicItems, betweenPool, betweenInterval, 'ezoic-ad-topic'));
|
|
315
225
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (isTopicPage()) injectInTopic();
|
|
320
|
-
else if (isCategoryTopicList()) injectInCategory();
|
|
321
|
-
} finally {
|
|
322
|
-
refreshInFlight = false;
|
|
323
|
-
if (refreshQueued) { refreshQueued = false; setTimeout(refresh, 50); }
|
|
226
|
+
callEzoic(newIds);
|
|
227
|
+
return;
|
|
324
228
|
}
|
|
325
|
-
}
|
|
326
229
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
// Small delayed refresh (DOM late), harmless: it won't call showAds if no new placeholders were injected
|
|
340
|
-
setTimeout(refresh, 1200);
|
|
341
|
-
});
|
|
230
|
+
if ($posts.length) {
|
|
231
|
+
if (cfg.enableMessageAds && messagePool.length) {
|
|
232
|
+
newIds.push(...injectMessageIncremental($posts, messagePool, messageInterval));
|
|
233
|
+
}
|
|
234
|
+
callEzoic(newIds);
|
|
235
|
+
}
|
|
236
|
+
} finally {
|
|
237
|
+
inFlight = false;
|
|
238
|
+
if (rerunRequested) {
|
|
239
|
+
rerunRequested = false;
|
|
240
|
+
setTimeout(refreshAds, 120);
|
|
241
|
+
}
|
|
342
242
|
}
|
|
243
|
+
}
|
|
343
244
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
// Infinite scroll related events
|
|
349
|
-
$(window).on('action:posts.loaded action:topic.loaded action:topics.loaded action:category.loaded', function () {
|
|
350
|
-
refresh();
|
|
351
|
-
setTimeout(refresh, 600);
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
// Navigation start: cleanup to avoid duplicates
|
|
355
|
-
$(window).on('action:ajaxify.start', function () {
|
|
356
|
-
pageKey = null;
|
|
357
|
-
cleanupForNewPage();
|
|
358
|
-
});
|
|
245
|
+
function debounceRefresh() {
|
|
246
|
+
clearTimeout(debounceTimer);
|
|
247
|
+
debounceTimer = setTimeout(refreshAds, 180);
|
|
248
|
+
}
|
|
359
249
|
|
|
360
|
-
|
|
250
|
+
$(document).ready(debounceRefresh);
|
|
251
|
+
$(window).on('action:ajaxify.end action:posts.loaded action:topic.loaded', debounceRefresh);
|
|
252
|
+
setTimeout(debounceRefresh, 1800);
|
|
@@ -1,59 +1,59 @@
|
|
|
1
1
|
<div class="acp-page-container">
|
|
2
|
-
<
|
|
2
|
+
<h2>Ezoic - Publicités Infinite Scroll Ads</h2>
|
|
3
3
|
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
</div>
|
|
4
|
+
<form class="ezoic-infinite-settings" role="form">
|
|
5
|
+
<h4 class="mt-3">Pubs entre les posts (bloc simple)</h4>
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
<label class="form-label">
|
|
11
|
-
<select multiple class="form-select" name="excludedGroups">
|
|
12
|
-
<!-- BEGIN groups -->
|
|
13
|
-
<option value="{groups.name}">{groups.name}</option>
|
|
14
|
-
<!-- END groups -->
|
|
15
|
-
</select>
|
|
16
|
-
<div class="form-text">Maintenez Ctrl/Cmd pour sélectionner plusieurs groupes. Liste triée par ordre alphabétique.</div>
|
|
17
|
-
</div>
|
|
18
|
-
|
|
19
|
-
<hr/>
|
|
20
|
-
|
|
21
|
-
<h3>Entre les topics dans une catégorie (liste des sujets)</h3>
|
|
22
|
-
|
|
23
|
-
<div class="form-check form-switch mb-2">
|
|
24
|
-
<input class="form-check-input" type="checkbox" name="enableBetweenAds">
|
|
25
|
-
<label class="form-check-label">Activer les pubs entre les topics</label>
|
|
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>
|
|
26
10
|
</div>
|
|
27
11
|
|
|
28
12
|
<div class="mb-3">
|
|
29
|
-
<label class="form-label">
|
|
30
|
-
<
|
|
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>
|
|
31
16
|
</div>
|
|
32
17
|
|
|
33
18
|
<div class="mb-3">
|
|
34
|
-
<label class="form-label">
|
|
35
|
-
<
|
|
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">
|
|
36
21
|
</div>
|
|
37
22
|
|
|
38
23
|
<hr/>
|
|
39
24
|
|
|
40
|
-
<
|
|
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>
|
|
41
27
|
|
|
42
|
-
<div class="form-check
|
|
43
|
-
<input class="form-check-input" type="checkbox" name="enableMessageAds">
|
|
44
|
-
<label class="form-check-label">Activer les pubs
|
|
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>
|
|
45
31
|
</div>
|
|
46
32
|
|
|
47
33
|
<div class="mb-3">
|
|
48
|
-
<label class="form-label">
|
|
49
|
-
<
|
|
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>
|
|
50
37
|
</div>
|
|
51
38
|
|
|
52
39
|
<div class="mb-3">
|
|
53
|
-
<label class="form-label">
|
|
54
|
-
<
|
|
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">
|
|
42
|
+
</div>
|
|
43
|
+
|
|
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>
|
|
55
55
|
</div>
|
|
56
56
|
|
|
57
|
-
<button
|
|
57
|
+
<button id="save" class="btn btn-primary">Enregistrer</button>
|
|
58
58
|
</form>
|
|
59
59
|
</div>
|
package/public/style.css
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
/* Le conteneur est caché tant qu'Ezoic n'a pas injecté de contenu */
|
|
2
|
-
.ezoic-ad{min-height:0 !important;height:auto !important;padding:0 !important;margin:0.5rem 0;}
|
|
3
|
-
.ezoic-ad:not(.ezoic-filled){display:none !important;}
|
|
4
|
-
.ezoic-ad .ezoic-ad-inner{padding:0;margin:0;}
|
|
5
|
-
.ezoic-ad .ezoic-ad-inner > div{margin:0;padding:0;}
|