nodebb-plugin-ezoic-infinite 0.9.7 → 0.9.8
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 +37 -77
- package/package.json +2 -5
- package/plugin.json +5 -2
- package/public/admin.js +48 -23
- package/public/client.js +289 -224
- package/public/style.css +5 -0
- package/public/templates/admin/plugins/ezoic-infinite.tpl +35 -35
package/library.js
CHANGED
|
@@ -2,96 +2,56 @@
|
|
|
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');
|
|
6
5
|
|
|
7
|
-
const
|
|
8
|
-
const plugin = {};
|
|
6
|
+
const Plugin = {};
|
|
9
7
|
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
if (Array.isArray(value)) return value;
|
|
13
|
-
return String(value).split(',').map(s => s.trim()).filter(Boolean);
|
|
14
|
-
}
|
|
8
|
+
Plugin.init = async function (params) {
|
|
9
|
+
const { router, middleware } = params;
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const s = String(v).toLowerCase();
|
|
20
|
-
return s === '1' || s === 'true' || s === 'on' || s === 'yes';
|
|
21
|
-
}
|
|
11
|
+
router.get('/admin/plugins/ezoic-infinite', middleware.admin.buildHeader, renderAdmin);
|
|
12
|
+
router.get('/api/admin/plugins/ezoic-infinite', renderAdmin);
|
|
13
|
+
};
|
|
22
14
|
|
|
23
|
-
async function
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
placeholderIds: (s.placeholderIds || '').trim(),
|
|
37
|
-
intervalPosts: Math.max(1, parseInt(s.intervalPosts, 10) || 6),
|
|
15
|
+
async function renderAdmin(req, res) {
|
|
16
|
+
const settings = await meta.settings.get('ezoic-infinite');
|
|
17
|
+
|
|
18
|
+
let groupNames = [];
|
|
19
|
+
try {
|
|
20
|
+
groupNames = await groups.getGroupsFromSet('groups:createtime', 0, -1);
|
|
21
|
+
} catch (e) {
|
|
22
|
+
try {
|
|
23
|
+
groupNames = await groups.getGroupsFromSet('groups:visible:createtime', 0, -1);
|
|
24
|
+
} catch (e2) {
|
|
25
|
+
groupNames = [];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
38
28
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
29
|
+
let groupList = [];
|
|
30
|
+
try {
|
|
31
|
+
groupList = await groups.getGroupsData(groupNames);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
groupList = groupNames.map((name) => ({ name }));
|
|
34
|
+
}
|
|
43
35
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
36
|
+
groupList = (groupList || [])
|
|
37
|
+
.filter(g => g && g.name)
|
|
38
|
+
.sort((a, b) => (a.name || '').localeCompare(b.name || '', 'fr', { sensitivity: 'base' }));
|
|
47
39
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
40
|
+
res.render('admin/plugins/ezoic-infinite', {
|
|
41
|
+
title: 'Ezoic - Publicités Infinite Scroll',
|
|
42
|
+
settings,
|
|
43
|
+
groups: groupList,
|
|
44
|
+
});
|
|
52
45
|
}
|
|
53
46
|
|
|
54
|
-
|
|
47
|
+
Plugin.addAdminNavigation = async function (header) {
|
|
55
48
|
header.plugins = header.plugins || [];
|
|
56
49
|
header.plugins.push({
|
|
57
50
|
route: '/plugins/ezoic-infinite',
|
|
58
|
-
icon: 'fa-
|
|
59
|
-
name: 'Ezoic Infinite
|
|
51
|
+
icon: 'fa-bullhorn',
|
|
52
|
+
name: 'Ezoic Infinite',
|
|
60
53
|
});
|
|
61
54
|
return header;
|
|
62
55
|
};
|
|
63
56
|
|
|
64
|
-
|
|
65
|
-
async function render(req, res) {
|
|
66
|
-
const settings = await getSettings();
|
|
67
|
-
const allGroups = await getAllGroups();
|
|
68
|
-
|
|
69
|
-
res.render('admin/plugins/ezoic-infinite', {
|
|
70
|
-
title: 'Ezoic Infinite Ads',
|
|
71
|
-
...settings,
|
|
72
|
-
enableBetweenAds_checked: settings.enableBetweenAds ? 'checked' : '',
|
|
73
|
-
enableMessageAds_checked: settings.enableMessageAds ? 'checked' : '',
|
|
74
|
-
allGroups,
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
router.get('/admin/plugins/ezoic-infinite', middleware.admin.buildHeader, render);
|
|
79
|
-
router.get('/api/admin/plugins/ezoic-infinite', render);
|
|
80
|
-
|
|
81
|
-
router.get('/api/plugins/ezoic-infinite/config', middleware.buildHeader, async (req, res) => {
|
|
82
|
-
const settings = await getSettings();
|
|
83
|
-
const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
|
|
84
|
-
|
|
85
|
-
res.json({
|
|
86
|
-
excluded,
|
|
87
|
-
enableBetweenAds: settings.enableBetweenAds,
|
|
88
|
-
placeholderIds: settings.placeholderIds,
|
|
89
|
-
intervalPosts: settings.intervalPosts,
|
|
90
|
-
enableMessageAds: settings.enableMessageAds,
|
|
91
|
-
messagePlaceholderIds: settings.messagePlaceholderIds,
|
|
92
|
-
messageIntervalPosts: settings.messageIntervalPosts,
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
module.exports = plugin;
|
|
57
|
+
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": "Ezoic
|
|
3
|
+
"version": "0.9.8",
|
|
4
|
+
"description": "Injection de publicités Ezoic entre les topics et entre les messages avec infinite scroll (NodeBB 4.x).",
|
|
5
5
|
"main": "library.js",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
@@ -11,9 +11,6 @@
|
|
|
11
11
|
"ads",
|
|
12
12
|
"infinite-scroll"
|
|
13
13
|
],
|
|
14
|
-
"engines": {
|
|
15
|
-
"node": ">=18"
|
|
16
|
-
},
|
|
17
14
|
"nbbpm": {
|
|
18
15
|
"compatibility": "^4.0.0"
|
|
19
16
|
}
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "nodebb-plugin-ezoic-infinite",
|
|
3
|
-
"name": "
|
|
4
|
-
"description": "Ezoic ads with infinite scroll
|
|
3
|
+
"name": "Ezoic Infinite",
|
|
4
|
+
"description": "Ezoic ads injection with infinite scroll (topics list + topic posts)",
|
|
5
5
|
"library": "./library.js",
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
@@ -22,5 +22,8 @@
|
|
|
22
22
|
"scripts": [
|
|
23
23
|
"public/client.js"
|
|
24
24
|
],
|
|
25
|
+
"css": [
|
|
26
|
+
"public/style.css"
|
|
27
|
+
],
|
|
25
28
|
"templates": "public/templates"
|
|
26
29
|
}
|
package/public/admin.js
CHANGED
|
@@ -1,29 +1,54 @@
|
|
|
1
|
-
/*
|
|
1
|
+
/* global $, app, socket */
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
(function () {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
$('
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
4
|
+
$(document).ready(function () {
|
|
5
|
+
const namespace = 'ezoic-infinite';
|
|
6
|
+
|
|
7
|
+
function load() {
|
|
8
|
+
socket.emit('admin.settings.get', { hash: namespace }, function (err, data) {
|
|
9
|
+
if (err) return;
|
|
10
|
+
data = data || {};
|
|
11
|
+
|
|
12
|
+
const form = $('.ezoic-infinite-settings');
|
|
13
|
+
|
|
14
|
+
form.find('[name="enableBetweenAds"]').prop('checked', data.enableBetweenAds === true || data.enableBetweenAds === 'on');
|
|
15
|
+
form.find('[name="intervalTopics"]').val(parseInt(data.intervalTopics, 10) || 6);
|
|
16
|
+
form.find('[name="placeholderIds"]').val(data.placeholderIds || '');
|
|
17
|
+
|
|
18
|
+
form.find('[name="enableMessageAds"]').prop('checked', data.enableMessageAds === true || data.enableMessageAds === 'on');
|
|
19
|
+
form.find('[name="messageIntervalPosts"]').val(parseInt(data.messageIntervalPosts, 10) || 3);
|
|
20
|
+
form.find('[name="messagePlaceholderIds"]').val(data.messagePlaceholderIds || '');
|
|
21
|
+
|
|
22
|
+
const selected = (data.excludedGroups || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
23
|
+
form.find('[name="excludedGroups"] option').each(function () {
|
|
24
|
+
$(this).prop('selected', selected.includes($(this).val()));
|
|
23
25
|
});
|
|
24
26
|
});
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
function save() {
|
|
30
|
+
const form = $('.ezoic-infinite-settings');
|
|
31
|
+
const payload = {
|
|
32
|
+
enableBetweenAds: form.find('[name="enableBetweenAds"]').is(':checked'),
|
|
33
|
+
intervalTopics: parseInt(form.find('[name="intervalTopics"]').val(), 10) || 6,
|
|
34
|
+
placeholderIds: form.find('[name="placeholderIds"]').val() || '',
|
|
35
|
+
|
|
36
|
+
enableMessageAds: form.find('[name="enableMessageAds"]').is(':checked'),
|
|
37
|
+
messageIntervalPosts: parseInt(form.find('[name="messageIntervalPosts"]').val(), 10) || 3,
|
|
38
|
+
messagePlaceholderIds: form.find('[name="messagePlaceholderIds"]').val() || '',
|
|
39
|
+
|
|
40
|
+
excludedGroups: (form.find('[name="excludedGroups"]').val() || []).join(','),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
socket.emit('admin.settings.set', { hash: namespace, values: payload }, function (err) {
|
|
44
|
+
if (err) {
|
|
45
|
+
app.alertError(err.message || err);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
app.alertSuccess('Paramètres enregistrés');
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
$('.ezoic-infinite-save').on('click', save);
|
|
53
|
+
load();
|
|
54
|
+
});
|
package/public/client.js
CHANGED
|
@@ -1,100 +1,114 @@
|
|
|
1
|
+
/* global $, ajaxify, app, socket */
|
|
1
2
|
'use strict';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
window.ezoicInfiniteLoaded
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
let
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
let
|
|
15
|
-
let
|
|
16
|
-
let
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
4
|
+
(function () {
|
|
5
|
+
if (window.ezoicInfiniteLoaded) return;
|
|
6
|
+
window.ezoicInfiniteLoaded = true;
|
|
7
|
+
|
|
8
|
+
const SETTINGS_NS = 'ezoic-infinite';
|
|
9
|
+
|
|
10
|
+
let settings = null;
|
|
11
|
+
let pageKey = null;
|
|
12
|
+
|
|
13
|
+
// State per page
|
|
14
|
+
let usedTopic = new Set(); // ids currently in DOM (topic)
|
|
15
|
+
let usedCat = new Set(); // ids currently in DOM (category)
|
|
16
|
+
let fifoTopic = []; // [{id, afterNo}]
|
|
17
|
+
let fifoCat = []; // [{id, afterPos}]
|
|
18
|
+
|
|
19
|
+
// Refresh single-flight
|
|
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) {
|
|
41
|
+
return false;
|
|
30
42
|
}
|
|
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
43
|
}
|
|
86
|
-
return '<div class="' + classes + '"' + attrStr + '>' + innerHtml + '</div>';
|
|
87
|
-
}
|
|
88
44
|
|
|
89
|
-
function
|
|
90
|
-
|
|
91
|
-
|
|
45
|
+
function getPageKey() {
|
|
46
|
+
try {
|
|
47
|
+
if (ajaxify && ajaxify.data) {
|
|
48
|
+
if (ajaxify.data.tid) return 'topic:' + ajaxify.data.tid;
|
|
49
|
+
if (ajaxify.data.cid) return 'cid:' + ajaxify.data.cid + ':' + window.location.pathname;
|
|
50
|
+
}
|
|
51
|
+
} catch (e) {}
|
|
52
|
+
return window.location.pathname;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isTopicPage() {
|
|
56
|
+
try { if (ajaxify && ajaxify.data && ajaxify.data.tid) return true; } catch (e) {}
|
|
57
|
+
return /^\/topic\//.test(window.location.pathname);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isCategoryTopicList() {
|
|
61
|
+
return $('li[component="category/topic"]').length > 0 && !isTopicPage();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function cleanupForNewPage() {
|
|
65
|
+
$('.ezoic-ad').remove();
|
|
66
|
+
usedTopic = new Set();
|
|
67
|
+
usedCat = new Set();
|
|
68
|
+
fifoTopic = [];
|
|
69
|
+
fifoCat = [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function pickNextId(pool, usedSet) {
|
|
73
|
+
for (const id of pool) {
|
|
74
|
+
if (!usedSet.has(id)) return id;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
92
77
|
}
|
|
93
|
-
|
|
94
|
-
|
|
78
|
+
|
|
79
|
+
function destroyPlaceholder(id) {
|
|
80
|
+
try {
|
|
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
|
+
}
|
|
92
|
+
|
|
93
|
+
function ensureUniquePlaceholder(id) {
|
|
94
|
+
const existing = document.getElementById('ezoic-pub-ad-placeholder-' + id);
|
|
95
|
+
if (!existing) return;
|
|
96
|
+
|
|
97
|
+
const wrap = existing.closest('.ezoic-ad');
|
|
98
|
+
if (wrap) {
|
|
99
|
+
try { $(wrap).remove(); } catch (e) { wrap.remove(); }
|
|
100
|
+
} else {
|
|
101
|
+
existing.remove();
|
|
102
|
+
}
|
|
103
|
+
// On détruit le slot correspondant pour éviter les comportements imprévisibles
|
|
104
|
+
destroyPlaceholder(id);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// IMPORTANT: showAds is called ONLY with newly injected ids (never the entire pool)
|
|
95
108
|
function callEzoic(ids) {
|
|
96
109
|
if (!ids || !ids.length) return;
|
|
97
110
|
|
|
111
|
+
// Anti double-call: same ids within 1.2s
|
|
98
112
|
const key = ids.slice().sort((a, b) => a - b).join(',');
|
|
99
113
|
const now = Date.now();
|
|
100
114
|
if (window.__ezoicLastShowKey === key && now - (window.__ezoicLastShowAt || 0) < 1200) return;
|
|
@@ -108,6 +122,7 @@ function pickNextId(pool) {
|
|
|
108
122
|
const run = function () {
|
|
109
123
|
try {
|
|
110
124
|
if (typeof window.ezstandalone.showAds === 'function') {
|
|
125
|
+
// Ezoic accepte plusieurs args: showAds(1,2,3)
|
|
111
126
|
window.ezstandalone.showAds.apply(window.ezstandalone, ids);
|
|
112
127
|
return true;
|
|
113
128
|
}
|
|
@@ -117,179 +132,229 @@ function pickNextId(pool) {
|
|
|
117
132
|
|
|
118
133
|
window.ezstandalone.cmd.push(function () { run(); });
|
|
119
134
|
|
|
135
|
+
// Retry a few times if ez loads late
|
|
120
136
|
let tries = 0;
|
|
121
137
|
const tick = function () {
|
|
122
138
|
tries++;
|
|
123
|
-
if (run() || tries >=
|
|
139
|
+
if (run() || tries >= 8) return;
|
|
124
140
|
setTimeout(tick, 800);
|
|
125
141
|
};
|
|
126
142
|
setTimeout(tick, 800);
|
|
127
143
|
} catch (e) {}
|
|
128
144
|
}
|
|
129
145
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
} catch (e) {}
|
|
135
|
-
return false;
|
|
136
|
-
};
|
|
146
|
+
// Auto height: wrapper visible only when placeholder gets children
|
|
147
|
+
function setupAutoHeightOnce() {
|
|
148
|
+
if (window.__ezoicAutoHeight) return;
|
|
149
|
+
window.__ezoicAutoHeight = true;
|
|
137
150
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (run() || tries >= 10) return;
|
|
146
|
-
setTimeout(tick, 800);
|
|
147
|
-
};
|
|
148
|
-
setTimeout(tick, 800);
|
|
149
|
-
} catch (e) {}
|
|
150
|
-
}
|
|
151
|
-
} catch (e) {}
|
|
152
|
-
return false;
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
window.ezstandalone.cmd.push(function () { run(); });
|
|
156
|
-
|
|
157
|
-
// retry a few times (Ezoic can load late)
|
|
158
|
-
let tries = 0;
|
|
159
|
-
const maxTries = 6;
|
|
160
|
-
const timer = setInterval(function () {
|
|
161
|
-
tries++;
|
|
162
|
-
if (run() || tries >= maxTries) clearInterval(timer);
|
|
163
|
-
}, 800);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function injectBetweenIncremental($items, pool, interval, wrapperClass) {
|
|
167
|
-
const total = $items.length;
|
|
168
|
-
const maxSlot = Math.floor(total / interval);
|
|
169
|
-
if (maxSlot <= 0) return [];
|
|
170
|
-
|
|
171
|
-
const newIds = [];
|
|
172
|
-
|
|
173
|
-
for (let slot = 1; slot <= maxSlot; slot++) {
|
|
174
|
-
if (injectedSlots.has(slot)) continue;
|
|
175
|
-
|
|
176
|
-
const index = slot * interval - 1;
|
|
177
|
-
const $target = $items.eq(index);
|
|
178
|
-
if (!$target.length) continue;
|
|
179
|
-
|
|
180
|
-
const id = pickNextId(pool);
|
|
181
|
-
if (!id) {
|
|
182
|
-
// pool exhausted: stop injecting further to avoid reusing ids and "jumping"
|
|
183
|
-
break;
|
|
184
|
-
}
|
|
151
|
+
const mark = function (wrap) {
|
|
152
|
+
if (!wrap) return;
|
|
153
|
+
const ph = wrap.querySelector('[id^="ezoic-pub-ad-placeholder-"]');
|
|
154
|
+
if (ph && ph.children && ph.children.length) {
|
|
155
|
+
wrap.classList.add('ezoic-filled');
|
|
156
|
+
}
|
|
157
|
+
};
|
|
185
158
|
|
|
186
|
-
const
|
|
187
|
-
|
|
159
|
+
const scan = function () {
|
|
160
|
+
document.querySelectorAll('.ezoic-ad').forEach(mark);
|
|
161
|
+
};
|
|
188
162
|
|
|
189
|
-
|
|
163
|
+
scan();
|
|
164
|
+
setInterval(scan, 1000);
|
|
190
165
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
166
|
+
try {
|
|
167
|
+
const mo = new MutationObserver(scan);
|
|
168
|
+
mo.observe(document.body, { childList: true, subtree: true });
|
|
169
|
+
} catch (e) {}
|
|
194
170
|
}
|
|
195
171
|
|
|
196
|
-
|
|
197
|
-
|
|
172
|
+
function insertAfter($target, id, kind, afterVal, cls) {
|
|
173
|
+
ensureUniquePlaceholder(id);
|
|
174
|
+
const wrap = $(
|
|
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;
|
|
181
|
+
}
|
|
198
182
|
|
|
199
|
-
function
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
183
|
+
function recycleTopic($posts) {
|
|
184
|
+
fifoTopic.sort((a, b) => a.afterNo - b.afterNo);
|
|
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
|
+
}
|
|
203
207
|
|
|
204
|
-
|
|
208
|
+
function recycleCat($items) {
|
|
209
|
+
fifoCat.sort((a, b) => a.afterPos - b.afterPos);
|
|
210
|
+
while (fifoCat.length) {
|
|
211
|
+
const old = fifoCat.shift();
|
|
212
|
+
const sel = '.ezoic-ad-between[data-ezoic-id="' + old.id + '"][data-ezoic-after="' + old.afterPos + '"]';
|
|
213
|
+
const $el = $(sel);
|
|
214
|
+
if (!$el.length) continue;
|
|
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;
|
|
230
|
+
}
|
|
205
231
|
|
|
206
|
-
|
|
207
|
-
if (
|
|
232
|
+
function injectInTopic() {
|
|
233
|
+
if (!(settings && (settings.enableMessageAds === true || settings.enableMessageAds === 'on'))) return;
|
|
208
234
|
|
|
209
|
-
const
|
|
210
|
-
const
|
|
211
|
-
if (
|
|
235
|
+
const interval = parseInt(settings.messageIntervalPosts, 10) || 3;
|
|
236
|
+
const pool = parsePool(settings.messagePlaceholderIds);
|
|
237
|
+
if (!pool.length) return;
|
|
212
238
|
|
|
213
|
-
const
|
|
214
|
-
if (
|
|
239
|
+
const $posts = $('[component="post"][data-pid]');
|
|
240
|
+
if (!$posts.length) return;
|
|
215
241
|
|
|
216
|
-
const
|
|
217
|
-
|
|
242
|
+
const newIds = [];
|
|
243
|
+
$posts.each(function (idx) {
|
|
244
|
+
const postNo = idx + 1;
|
|
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
|
+
}
|
|
218
257
|
|
|
219
|
-
|
|
258
|
+
usedTopic.add(id);
|
|
259
|
+
fifoTopic.push({ id, afterNo: postNo });
|
|
220
260
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
261
|
+
insertAfter($post, id, 'topic', postNo, 'ezoic-ad-topic');
|
|
262
|
+
newIds.push(id);
|
|
263
|
+
});
|
|
225
264
|
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
async function refreshAds() {
|
|
230
|
-
// reset state when navigating (ajaxify)
|
|
231
|
-
const key = currentPageKey();
|
|
232
|
-
if (pageKey !== key) {
|
|
233
|
-
pageKey = key;
|
|
234
|
-
resetPageState();
|
|
235
|
-
// also cleanup any injected wrappers that may have been left by browser bfcache
|
|
236
|
-
$('.ezoic-ad-post, .ezoic-ad-between, .ezoic-ad-topic').remove();
|
|
265
|
+
if (newIds.length) callEzoic(newIds);
|
|
237
266
|
}
|
|
238
267
|
|
|
239
|
-
|
|
240
|
-
|
|
268
|
+
function injectInCategory() {
|
|
269
|
+
if (!(settings && (settings.enableBetweenAds === true || settings.enableBetweenAds === 'on'))) return;
|
|
241
270
|
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
if (!
|
|
271
|
+
const interval = parseInt(settings.intervalTopics, 10) || 6;
|
|
272
|
+
const pool = parsePool(settings.placeholderIds);
|
|
273
|
+
if (!pool.length) return;
|
|
245
274
|
|
|
246
|
-
const
|
|
247
|
-
|
|
275
|
+
const $items = $('li[component="category/topic"]');
|
|
276
|
+
if (!$items.length) return;
|
|
248
277
|
|
|
249
|
-
const
|
|
250
|
-
|
|
278
|
+
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
|
+
}
|
|
251
293
|
|
|
252
|
-
|
|
253
|
-
|
|
294
|
+
usedCat.add(id);
|
|
295
|
+
fifoCat.push({ id, afterPos: pos });
|
|
254
296
|
|
|
255
|
-
|
|
256
|
-
|
|
297
|
+
insertAfter($li, id, 'between', pos, 'ezoic-ad-between');
|
|
298
|
+
newIds.push(id);
|
|
299
|
+
});
|
|
257
300
|
|
|
258
|
-
if (
|
|
301
|
+
if (newIds.length) callEzoic(newIds);
|
|
302
|
+
}
|
|
259
303
|
|
|
260
|
-
|
|
304
|
+
function refresh() {
|
|
305
|
+
if (!settings) return;
|
|
306
|
+
if (userExcluded()) return;
|
|
261
307
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if (
|
|
267
|
-
|
|
308
|
+
if (refreshInFlight) { refreshQueued = true; return; }
|
|
309
|
+
refreshInFlight = true;
|
|
310
|
+
try {
|
|
311
|
+
const key = getPageKey();
|
|
312
|
+
if (pageKey !== key) {
|
|
313
|
+
pageKey = key;
|
|
314
|
+
cleanupForNewPage();
|
|
268
315
|
}
|
|
269
|
-
callEzoic(newIds);
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
316
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
inFlight = false;
|
|
281
|
-
if (rerunRequested) {
|
|
282
|
-
rerunRequested = false;
|
|
283
|
-
setTimeout(refreshAds, 120);
|
|
317
|
+
setupAutoHeightOnce();
|
|
318
|
+
|
|
319
|
+
if (isTopicPage()) injectInTopic();
|
|
320
|
+
else if (isCategoryTopicList()) injectInCategory();
|
|
321
|
+
} finally {
|
|
322
|
+
refreshInFlight = false;
|
|
323
|
+
if (refreshQueued) { refreshQueued = false; setTimeout(refresh, 50); }
|
|
284
324
|
}
|
|
285
325
|
}
|
|
286
|
-
}
|
|
287
326
|
|
|
288
|
-
function
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
327
|
+
function loadSettings(cb) {
|
|
328
|
+
// We read settings via socket so it works in ACP changes without restart
|
|
329
|
+
socket.emit('admin.settings.get', { hash: SETTINGS_NS }, function (err, data) {
|
|
330
|
+
settings = data || {};
|
|
331
|
+
cb && cb();
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Boot: load settings then refresh. No showAds batch here.
|
|
336
|
+
function boot() {
|
|
337
|
+
loadSettings(function () {
|
|
338
|
+
refresh();
|
|
339
|
+
// Small delayed refresh (DOM late), harmless: it won't call showAds if no new placeholders were injected
|
|
340
|
+
setTimeout(refresh, 1200);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Hard load + SPA
|
|
345
|
+
$(document).ready(boot);
|
|
346
|
+
$(window).on('action:ajaxify.end', boot);
|
|
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
|
+
});
|
|
292
359
|
|
|
293
|
-
|
|
294
|
-
$(window).on('action:ajaxify.end action:posts.loaded action:topic.loaded', debounceRefresh);
|
|
295
|
-
setTimeout(debounceRefresh, 1800);
|
|
360
|
+
})();
|
package/public/style.css
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
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;}
|
|
@@ -1,59 +1,59 @@
|
|
|
1
1
|
<div class="acp-page-container">
|
|
2
|
-
<
|
|
2
|
+
<h1 class="mb-3">Ezoic - Publicités Infinite Scroll</h1>
|
|
3
3
|
|
|
4
|
-
<
|
|
5
|
-
<
|
|
6
|
-
|
|
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>
|
|
4
|
+
<div class="alert alert-info">
|
|
5
|
+
Format placeholder : <code><div id="ezoic-pub-ad-placeholder-XXX"></div></code>
|
|
6
|
+
</div>
|
|
11
7
|
|
|
8
|
+
<form role="form" class="ezoic-infinite-settings">
|
|
12
9
|
<div class="mb-3">
|
|
13
|
-
<label class="form-label"
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
<
|
|
20
|
-
<input type="number" id="intervalPosts" name="intervalPosts" class="form-control" value="{intervalPosts}" min="1">
|
|
10
|
+
<label class="form-label">Groupes exclus (pas de pubs pour ces groupes)</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>
|
|
21
17
|
</div>
|
|
22
18
|
|
|
23
19
|
<hr/>
|
|
24
20
|
|
|
25
|
-
<
|
|
26
|
-
<p class="form-text">Insère un bloc qui ressemble à un post, toutes les N réponses (dans une page topic).</p>
|
|
21
|
+
<h3>Entre les topics dans une catégorie (liste des sujets)</h3>
|
|
27
22
|
|
|
28
|
-
<div class="form-check mb-
|
|
29
|
-
<input class="form-check-input" type="checkbox"
|
|
30
|
-
<label class="form-check-label"
|
|
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>
|
|
31
26
|
</div>
|
|
32
27
|
|
|
33
28
|
<div class="mb-3">
|
|
34
|
-
<label class="form-label"
|
|
35
|
-
<
|
|
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>
|
|
29
|
+
<label class="form-label">Intervalle (insérer après chaque N topics)</label>
|
|
30
|
+
<input type="number" class="form-control" name="intervalTopics" min="1" step="1">
|
|
37
31
|
</div>
|
|
38
32
|
|
|
39
33
|
<div class="mb-3">
|
|
40
|
-
<label class="form-label"
|
|
41
|
-
<
|
|
34
|
+
<label class="form-label">Pool d'IDs de placeholder (un par ligne)</label>
|
|
35
|
+
<textarea class="form-control" name="placeholderIds" rows="6"></textarea>
|
|
42
36
|
</div>
|
|
43
37
|
|
|
44
38
|
<hr/>
|
|
45
39
|
|
|
46
|
-
<
|
|
40
|
+
<h3>Dans les topics (entre les messages)</h3>
|
|
41
|
+
|
|
42
|
+
<div class="form-check form-switch mb-2">
|
|
43
|
+
<input class="form-check-input" type="checkbox" name="enableMessageAds">
|
|
44
|
+
<label class="form-check-label">Activer les pubs entre les messages</label>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
47
|
<div class="mb-3">
|
|
48
|
-
<label class="form-label"
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
</
|
|
54
|
-
<
|
|
48
|
+
<label class="form-label">Intervalle (insérer après chaque N messages)</label>
|
|
49
|
+
<input type="number" class="form-control" name="messageIntervalPosts" min="1" step="1">
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div class="mb-3">
|
|
53
|
+
<label class="form-label">Pool d'IDs de placeholder pour messages (un par ligne)</label>
|
|
54
|
+
<textarea class="form-control" name="messagePlaceholderIds" rows="6"></textarea>
|
|
55
55
|
</div>
|
|
56
56
|
|
|
57
|
-
<button
|
|
57
|
+
<button type="button" class="btn btn-primary ezoic-infinite-save">Enregistrer</button>
|
|
58
58
|
</form>
|
|
59
59
|
</div>
|