nodebb-plugin-ezoic-infinite 0.9.0 → 0.9.1
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 +20 -80
- package/package.json +2 -5
- package/public/admin.js +45 -23
- package/public/client.js +278 -390
- package/public/style.css +2 -2
- package/public/templates/admin/plugins/ezoic-infinite.tpl +35 -35
package/library.js
CHANGED
|
@@ -2,96 +2,36 @@
|
|
|
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
|
-
}
|
|
15
|
-
|
|
16
|
-
function parseBool(v, def = false) {
|
|
17
|
-
if (v === undefined || v === null || v === '') return def;
|
|
18
|
-
if (typeof v === 'boolean') return v;
|
|
19
|
-
const s = String(v).toLowerCase();
|
|
20
|
-
return s === '1' || s === 'true' || s === 'on' || s === 'yes';
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function getAllGroups() {
|
|
24
|
-
const names = await db.getSortedSetRange('groups:createtime', 0, -1);
|
|
25
|
-
const filtered = names.filter(name => !groups.isPrivilegeGroup(name));
|
|
26
|
-
const data = await groups.getGroupsData(filtered);
|
|
27
|
-
// Sort alphabetically for ACP usability
|
|
28
|
-
data.sort((a, b) => String(a.name).localeCompare(String(b.name), 'fr', { sensitivity: 'base' }));
|
|
29
|
-
return data;
|
|
30
|
-
}
|
|
31
|
-
async function getSettings() {
|
|
32
|
-
const s = await meta.settings.get(SETTINGS_KEY);
|
|
33
|
-
return {
|
|
34
|
-
// Between-post ads (simple blocks)
|
|
35
|
-
enableBetweenAds: parseBool(s.enableBetweenAds, true),
|
|
36
|
-
placeholderIds: (s.placeholderIds || '').trim(),
|
|
37
|
-
intervalPosts: Math.max(1, parseInt(s.intervalPosts, 10) || 6),
|
|
38
|
-
|
|
39
|
-
// "Ad message" between replies (looks like a post)
|
|
40
|
-
enableMessageAds: parseBool(s.enableMessageAds, false),
|
|
41
|
-
messagePlaceholderIds: (s.messagePlaceholderIds || '').trim(),
|
|
42
|
-
messageIntervalPosts: Math.max(1, parseInt(s.messageIntervalPosts, 10) || 3),
|
|
8
|
+
Plugin.init = async function (params) {
|
|
9
|
+
const { router, middleware } = params;
|
|
43
10
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
11
|
+
router.get('/admin/plugins/ezoic-infinite', middleware.admin.buildHeader, renderAdmin);
|
|
12
|
+
router.get('/api/admin/plugins/ezoic-infinite', renderAdmin);
|
|
13
|
+
};
|
|
47
14
|
|
|
48
|
-
async function
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
15
|
+
async function renderAdmin(req, res) {
|
|
16
|
+
const settings = await meta.settings.get('ezoic-infinite');
|
|
17
|
+
// groups list for exclusions
|
|
18
|
+
const groupList = await groups.getGroups('groups:visible:createtime', 0, -1);
|
|
19
|
+
groupList.sort((a, b) => (a.name || '').localeCompare(b.name || '', undefined, { sensitivity: 'base' }));
|
|
20
|
+
res.render('admin/plugins/ezoic-infinite', {
|
|
21
|
+
title: 'Ezoic Infinite',
|
|
22
|
+
settings,
|
|
23
|
+
groups: groupList,
|
|
24
|
+
});
|
|
52
25
|
}
|
|
53
26
|
|
|
54
|
-
|
|
27
|
+
Plugin.addAdminNavigation = async function (header) {
|
|
55
28
|
header.plugins = header.plugins || [];
|
|
56
29
|
header.plugins.push({
|
|
57
30
|
route: '/plugins/ezoic-infinite',
|
|
58
|
-
icon: 'fa-
|
|
59
|
-
name: 'Ezoic Infinite
|
|
31
|
+
icon: 'fa-bullhorn',
|
|
32
|
+
name: 'Ezoic Infinite',
|
|
60
33
|
});
|
|
61
34
|
return header;
|
|
62
35
|
};
|
|
63
36
|
|
|
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;
|
|
37
|
+
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 ads
|
|
3
|
+
"version": "0.9.1",
|
|
4
|
+
"description": "Ezoic ads injection for NodeBB infinite scroll (topics list + topic posts) using a pool of placeholder IDs.",
|
|
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/public/admin.js
CHANGED
|
@@ -1,29 +1,51 @@
|
|
|
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
|
+
form.find('[name="enableBetweenAds"]').prop('checked', data.enableBetweenAds === true || data.enableBetweenAds === 'on');
|
|
14
|
+
form.find('[name="intervalTopics"]').val(parseInt(data.intervalTopics, 10) || 6);
|
|
15
|
+
form.find('[name="placeholderIds"]').val(data.placeholderIds || '');
|
|
16
|
+
|
|
17
|
+
form.find('[name="enableMessageAds"]').prop('checked', data.enableMessageAds === true || data.enableMessageAds === 'on');
|
|
18
|
+
form.find('[name="messageIntervalPosts"]').val(parseInt(data.messageIntervalPosts, 10) || 3);
|
|
19
|
+
form.find('[name="messagePlaceholderIds"]').val(data.messagePlaceholderIds || '');
|
|
20
|
+
|
|
21
|
+
const selected = (data.excludedGroups || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
22
|
+
form.find('[name="excludedGroups"] option').each(function () {
|
|
23
|
+
$(this).prop('selected', selected.includes($(this).val()));
|
|
23
24
|
});
|
|
24
25
|
});
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
function save() {
|
|
29
|
+
const form = $('.ezoic-infinite-settings');
|
|
30
|
+
const payload = {
|
|
31
|
+
enableBetweenAds: form.find('[name="enableBetweenAds"]').is(':checked'),
|
|
32
|
+
intervalTopics: parseInt(form.find('[name="intervalTopics"]').val(), 10) || 6,
|
|
33
|
+
placeholderIds: form.find('[name="placeholderIds"]').val() || '',
|
|
34
|
+
enableMessageAds: form.find('[name="enableMessageAds"]').is(':checked'),
|
|
35
|
+
messageIntervalPosts: parseInt(form.find('[name="messageIntervalPosts"]').val(), 10) || 3,
|
|
36
|
+
messagePlaceholderIds: form.find('[name="messagePlaceholderIds"]').val() || '',
|
|
37
|
+
excludedGroups: (form.find('[name="excludedGroups"]').val() || []).join(','),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
socket.emit('admin.settings.set', { hash: namespace, values: payload }, function (err) {
|
|
41
|
+
if (err) {
|
|
42
|
+
app.alertError(err.message || err);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
app.alertSuccess('Settings saved');
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
$('.ezoic-infinite-save').on('click', save);
|
|
50
|
+
load();
|
|
51
|
+
});
|
package/public/client.js
CHANGED
|
@@ -1,443 +1,331 @@
|
|
|
1
|
+
/* global $, ajaxify, app */
|
|
1
2
|
'use strict';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
window.ezoicInfiniteLoaded
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
let
|
|
10
|
-
let
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
let
|
|
16
|
-
let
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
const res = await fetch('/api/admin/settings/ezoic-infinite', { credentials: 'same-origin' });
|
|
35
|
-
const data = await res.json();
|
|
36
|
-
cachedConfig = {
|
|
37
|
-
excluded: !!data.excluded,
|
|
38
|
-
enableBetweenAds: data.enableBetweenAds !== false,
|
|
39
|
-
placeholderIds: String(data.placeholderIds || '').trim(),
|
|
40
|
-
intervalPosts: parseInt(data.intervalPosts, 10) || 6,
|
|
41
|
-
|
|
42
|
-
enableMessageAds: data.enableMessageAds !== false,
|
|
43
|
-
messagePlaceholderIds: String(data.messagePlaceholderIds || '').trim(),
|
|
44
|
-
messageIntervalPosts: parseInt(data.messageIntervalPosts, 10) || 3,
|
|
45
|
-
};
|
|
46
|
-
return cachedConfig;
|
|
47
|
-
} catch (e) {
|
|
48
|
-
return cachedConfig || {
|
|
49
|
-
excluded: false,
|
|
50
|
-
enableBetweenAds: true,
|
|
51
|
-
placeholderIds: '',
|
|
52
|
-
intervalPosts: 6,
|
|
53
|
-
enableMessageAds: true,
|
|
54
|
-
messagePlaceholderIds: '',
|
|
55
|
-
messageIntervalPosts: 3,
|
|
56
|
-
};
|
|
3
|
+
|
|
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
|
+
// Separate pools for category/topic
|
|
14
|
+
let usedTopic = new Set();
|
|
15
|
+
let usedCat = new Set();
|
|
16
|
+
let fifoTopic = []; // [{id, afterNo}]
|
|
17
|
+
let fifoCat = []; // [{id, afterPos}]
|
|
18
|
+
|
|
19
|
+
function parsePool(text) {
|
|
20
|
+
return String(text || '')
|
|
21
|
+
.split(/\r?\n/)
|
|
22
|
+
.map(s => s.trim())
|
|
23
|
+
.filter(Boolean)
|
|
24
|
+
.map(s => parseInt(s, 10))
|
|
25
|
+
.filter(n => Number.isFinite(n) && n > 0);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function loadSettings(cb) {
|
|
29
|
+
socket.emit('admin.settings.get', { hash: SETTINGS_NS }, function (err, data) {
|
|
30
|
+
settings = data || {};
|
|
31
|
+
cb && cb();
|
|
32
|
+
});
|
|
57
33
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// ---------- Page detection ----------
|
|
70
|
-
function isTopicPage() {
|
|
71
|
-
try {
|
|
72
|
-
if (ajaxify && ajaxify.data && ajaxify.data.tid) return true;
|
|
73
|
-
} catch (e) {}
|
|
74
|
-
return /^\/topic\//.test(window.location.pathname);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function isCategoryTopicListPage() {
|
|
78
|
-
return document.querySelectorAll('li[component="category/topic"]').length > 0;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function getPageKey() {
|
|
82
|
-
try {
|
|
83
|
-
if (ajaxify && ajaxify.data) {
|
|
84
|
-
if (ajaxify.data.tid) return `topic:${ajaxify.data.tid}`;
|
|
85
|
-
if (ajaxify.data.cid) return `category:${ajaxify.data.cid}`;
|
|
34
|
+
|
|
35
|
+
function userExcluded() {
|
|
36
|
+
try {
|
|
37
|
+
const raw = (settings && settings.excludedGroups) ? String(settings.excludedGroups) : '';
|
|
38
|
+
if (!raw) return false;
|
|
39
|
+
const excluded = raw.split(',').map(s => s.trim()).filter(Boolean);
|
|
40
|
+
if (!excluded.length) return false;
|
|
41
|
+
const myGroups = (app.user && app.user.groups) ? app.user.groups : [];
|
|
42
|
+
return excluded.some(g => myGroups.includes(g));
|
|
43
|
+
} catch (e) {
|
|
44
|
+
return false;
|
|
86
45
|
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getPageKey() {
|
|
49
|
+
try {
|
|
50
|
+
if (ajaxify && ajaxify.data) {
|
|
51
|
+
if (ajaxify.data.tid) return 'topic:' + ajaxify.data.tid;
|
|
52
|
+
if (ajaxify.data.cid) return 'cid:' + ajaxify.data.cid + ':' + window.location.pathname;
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {}
|
|
55
|
+
return window.location.pathname;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isTopicPage() {
|
|
59
|
+
try { if (ajaxify && ajaxify.data && ajaxify.data.tid) return true; } catch (e) {}
|
|
60
|
+
return /^\/topic\//.test(window.location.pathname);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isCategoryTopicList() {
|
|
64
|
+
return $('li[component="category/topic"]').length > 0 && !isTopicPage();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function cleanupForNewPage() {
|
|
68
|
+
$('.ezoic-ad').remove();
|
|
69
|
+
usedTopic = new Set();
|
|
70
|
+
usedCat = new Set();
|
|
71
|
+
fifoTopic = [];
|
|
72
|
+
fifoCat = [];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function pickNextId(pool, usedSet) {
|
|
76
|
+
for (const id of pool) {
|
|
77
|
+
if (!usedSet.has(id)) return id;
|
|
117
78
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// Auto-height: hide until filled
|
|
130
|
-
function setupAdAutoHeight() {
|
|
131
|
-
if (window.__ezoicAutoHeightAttached) return;
|
|
132
|
-
window.__ezoicAutoHeightAttached = true;
|
|
133
|
-
|
|
134
|
-
const attach = function () {
|
|
135
|
-
document.querySelectorAll('.ezoic-ad').forEach(function (wrap) {
|
|
136
|
-
if (!wrap.classList.contains('ezoic-filled')) {
|
|
137
|
-
wrap.style.display = 'none';
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function destroyPlaceholder(id) {
|
|
83
|
+
try {
|
|
84
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
85
|
+
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
86
|
+
if (typeof window.ezstandalone.destroyPlaceholders === 'function') {
|
|
87
|
+
window.ezstandalone.destroyPlaceholders(id);
|
|
88
|
+
return;
|
|
138
89
|
}
|
|
90
|
+
window.ezstandalone.cmd.push(function () {
|
|
91
|
+
try { window.ezstandalone.destroyPlaceholders(id); } catch (e) {}
|
|
92
|
+
});
|
|
93
|
+
} catch (e) {}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function callEzoic(ids) {
|
|
97
|
+
if (!ids || !ids.length) return;
|
|
98
|
+
try {
|
|
99
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
100
|
+
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
101
|
+
const run = function () {
|
|
102
|
+
try {
|
|
103
|
+
if (typeof window.ezstandalone.showAds === 'function') {
|
|
104
|
+
window.ezstandalone.showAds.apply(window.ezstandalone, ids);
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
} catch (e) {}
|
|
108
|
+
return false;
|
|
109
|
+
};
|
|
110
|
+
window.ezstandalone.cmd.push(function () { run(); });
|
|
111
|
+
// retries in case ez loads late
|
|
112
|
+
let tries = 0;
|
|
113
|
+
const tick = function () {
|
|
114
|
+
tries++;
|
|
115
|
+
if (run() || tries >= 10) return;
|
|
116
|
+
setTimeout(tick, 800);
|
|
117
|
+
};
|
|
118
|
+
setTimeout(tick, 800);
|
|
119
|
+
} catch (e) {}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Auto height: show wrapper only when placeholder gets children
|
|
123
|
+
function setupAutoHeight() {
|
|
124
|
+
if (window.__ezoicAutoHeight) return;
|
|
125
|
+
window.__ezoicAutoHeight = true;
|
|
126
|
+
|
|
127
|
+
const mark = function (wrap) {
|
|
128
|
+
if (!wrap) return;
|
|
139
129
|
const ph = wrap.querySelector('[id^="ezoic-pub-ad-placeholder-"]');
|
|
140
130
|
if (ph && ph.children && ph.children.length) {
|
|
141
131
|
wrap.classList.add('ezoic-filled');
|
|
142
|
-
wrap.style.display = '';
|
|
143
132
|
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
function callEzoic(ids) {
|
|
155
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
156
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
157
|
-
|
|
158
|
-
const uniq = Array.from(new Set(ids || []));
|
|
159
|
-
if (!uniq.length) return;
|
|
160
|
-
|
|
161
|
-
// De-dupe rapid duplicates
|
|
162
|
-
const key = uniq.join(',');
|
|
163
|
-
const now = Date.now();
|
|
164
|
-
if (window.__ezoicLastShowKey === key && (now - (window.__ezoicLastShowAt || 0)) < 1200) return;
|
|
165
|
-
window.__ezoicLastShowKey = key;
|
|
166
|
-
window.__ezoicLastShowAt = now;
|
|
167
|
-
|
|
168
|
-
const run = function () {
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const scan = function () {
|
|
136
|
+
document.querySelectorAll('.ezoic-ad').forEach(mark);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
scan();
|
|
140
|
+
setInterval(scan, 1000);
|
|
141
|
+
|
|
169
142
|
try {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
143
|
+
const mo = new MutationObserver(scan);
|
|
144
|
+
mo.observe(document.body, { childList: true, subtree: true });
|
|
174
145
|
} catch (e) {}
|
|
175
|
-
|
|
176
|
-
};
|
|
146
|
+
}
|
|
177
147
|
|
|
178
|
-
|
|
179
|
-
|
|
148
|
+
function insertAfter($target, id, kind, afterVal) {
|
|
149
|
+
const wrap = $(
|
|
150
|
+
'<div class="ezoic-ad ezoic-ad-' + kind + '" data-ezoic-id="' + id + '" data-ezoic-after="' + afterVal + '">' +
|
|
151
|
+
'<div class="ezoic-ad-inner"><div id="ezoic-pub-ad-placeholder-' + id + '"></div></div>' +
|
|
152
|
+
'</div>'
|
|
153
|
+
);
|
|
154
|
+
$target.after(wrap);
|
|
155
|
+
return wrap;
|
|
156
|
+
}
|
|
180
157
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (tries < 6) setTimeout(retry, 800);
|
|
189
|
-
};
|
|
190
|
-
setTimeout(retry, 800);
|
|
191
|
-
}
|
|
192
|
-
} catch (e) {}
|
|
193
|
-
}
|
|
158
|
+
function recycleTopic($posts) {
|
|
159
|
+
fifoTopic.sort((a,b) => a.afterNo - b.afterNo);
|
|
160
|
+
while (fifoTopic.length) {
|
|
161
|
+
const old = fifoTopic.shift();
|
|
162
|
+
const sel = '.ezoic-ad-topic[data-ezoic-id="' + old.id + '"][data-ezoic-after="' + old.afterNo + '"]';
|
|
163
|
+
const $el = $(sel);
|
|
164
|
+
if (!$el.length) continue;
|
|
194
165
|
|
|
195
|
-
//
|
|
196
|
-
function pickNextId(pool, usedSet) {
|
|
197
|
-
for (const id of pool) {
|
|
198
|
-
if (!usedSet.has(id)) return id;
|
|
199
|
-
}
|
|
200
|
-
return null;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function recycleOldest(fifo, usedSet, selector, avoidAfterNode) {
|
|
204
|
-
if (window.__ezoicRecycling) return null;
|
|
205
|
-
window.__ezoicRecycling = true;
|
|
206
|
-
try {
|
|
207
|
-
fifo.sort((a, b) => (a.after - b.after));
|
|
208
|
-
while (fifo.length) {
|
|
209
|
-
const old = fifo.shift();
|
|
210
|
-
const el = document.querySelector(selector(old));
|
|
211
|
-
if (!el) continue;
|
|
212
|
-
|
|
213
|
-
// Don't recycle if it is right after the last real item (protect sentinel)
|
|
166
|
+
// don't recycle if this is right before the sentinel (after last post)
|
|
214
167
|
try {
|
|
215
|
-
|
|
216
|
-
|
|
168
|
+
const $last = $posts.last();
|
|
169
|
+
if ($last.length && $el.prev().is($last)) {
|
|
170
|
+
fifoTopic.push(old);
|
|
217
171
|
return null;
|
|
218
172
|
}
|
|
219
173
|
} catch (e) {}
|
|
220
174
|
|
|
221
|
-
el.remove();
|
|
222
|
-
|
|
223
|
-
|
|
175
|
+
$el.remove();
|
|
176
|
+
usedTopic.delete(old.id);
|
|
177
|
+
destroyPlaceholder(old.id);
|
|
224
178
|
return old.id;
|
|
225
179
|
}
|
|
226
180
|
return null;
|
|
227
|
-
} finally {
|
|
228
|
-
window.__ezoicRecycling = false;
|
|
229
181
|
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// ---------- Injection ----------
|
|
233
|
-
function injectBetweenAds(config) {
|
|
234
|
-
if (!config.enableBetweenAds) return;
|
|
235
|
-
const pool = parsePool(config.placeholderIds);
|
|
236
|
-
if (!pool.length) return;
|
|
237
|
-
|
|
238
|
-
const interval = Math.max(1, config.intervalPosts);
|
|
239
|
-
const items = Array.from(document.querySelectorAll('li[component="category/topic"]'));
|
|
240
|
-
if (!items.length) return;
|
|
241
|
-
|
|
242
|
-
const newIds = [];
|
|
243
|
-
const lastItem = items[items.length - 1];
|
|
244
|
-
|
|
245
|
-
for (let i = 0; i < items.length; i++) {
|
|
246
|
-
const pos = i + 1; // absolute position in loaded list
|
|
247
|
-
if (pos % interval !== 0) continue;
|
|
248
|
-
if (seenBetweenAfter.has(pos)) continue;
|
|
249
|
-
|
|
250
|
-
let id = pickNextId(pool, usedBetween);
|
|
251
|
-
if (!id) {
|
|
252
|
-
// recycle oldest
|
|
253
|
-
id = recycleOldest(
|
|
254
|
-
fifoBetween,
|
|
255
|
-
usedBetween,
|
|
256
|
-
(o) => `.ezoic-ad-topic[data-ezoic-after="${o.after}"][data-ezoic-id="${o.id}"]`,
|
|
257
|
-
lastItem
|
|
258
|
-
);
|
|
259
|
-
if (!id) break;
|
|
260
|
-
}
|
|
261
182
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
183
|
+
function recycleCat($items) {
|
|
184
|
+
fifoCat.sort((a,b) => a.afterPos - b.afterPos);
|
|
185
|
+
while (fifoCat.length) {
|
|
186
|
+
const old = fifoCat.shift();
|
|
187
|
+
const sel = '.ezoic-ad-between[data-ezoic-id="' + old.id + '"][data-ezoic-after="' + old.afterPos + '"]';
|
|
188
|
+
const $el = $(sel);
|
|
189
|
+
if (!$el.length) continue;
|
|
268
190
|
|
|
269
|
-
|
|
191
|
+
try {
|
|
192
|
+
const $last = $items.last();
|
|
193
|
+
if ($last.length && $el.prev().is($last)) {
|
|
194
|
+
fifoCat.push(old);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
} catch (e) {}
|
|
270
198
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
199
|
+
$el.remove();
|
|
200
|
+
usedCat.delete(old.id);
|
|
201
|
+
destroyPlaceholder(old.id);
|
|
202
|
+
return old.id;
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
275
205
|
}
|
|
276
206
|
|
|
277
|
-
|
|
278
|
-
|
|
207
|
+
function injectInTopic() {
|
|
208
|
+
if (!(settings && (settings.enableMessageAds === true || settings.enableMessageAds === 'on'))) return;
|
|
209
|
+
const interval = parseInt(settings.messageIntervalPosts, 10) || 3;
|
|
210
|
+
const pool = parsePool(settings.messagePlaceholderIds);
|
|
211
|
+
if (!pool.length) return;
|
|
279
212
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
const
|
|
286
|
-
if (
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
// Fallback: pid-based ordering not reliable, but DOM order is ok for interval within loaded window.
|
|
291
|
-
return null;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function injectMessageAds(config) {
|
|
295
|
-
if (!config.enableMessageAds) return;
|
|
296
|
-
const pool = parsePool(config.messagePlaceholderIds);
|
|
297
|
-
if (!pool.length) return;
|
|
298
|
-
|
|
299
|
-
const interval = Math.max(1, config.messageIntervalPosts);
|
|
300
|
-
const posts = Array.from(document.querySelectorAll('[component="post"][data-pid]'));
|
|
301
|
-
if (!posts.length) return;
|
|
302
|
-
|
|
303
|
-
const newIds = [];
|
|
304
|
-
const lastPost = posts[posts.length - 1];
|
|
305
|
-
|
|
306
|
-
// Determine absolute post numbers if available; else use DOM position
|
|
307
|
-
let numbers = posts.map((p, i) => ({ el: p, no: getPostNumberFromPost(p) || (i + 1) }));
|
|
308
|
-
// Ensure strictly increasing by DOM
|
|
309
|
-
numbers = numbers.map((x, i) => ({ el: x.el, no: x.no || (i + 1) }));
|
|
310
|
-
|
|
311
|
-
for (const entry of numbers) {
|
|
312
|
-
const afterNo = entry.no;
|
|
313
|
-
if (afterNo % interval !== 0) continue;
|
|
314
|
-
if (seenMsgAfter.has(afterNo)) continue;
|
|
315
|
-
|
|
316
|
-
let id = pickNextId(pool, usedMessages);
|
|
317
|
-
if (!id) {
|
|
318
|
-
id = recycleOldest(
|
|
319
|
-
fifoMsg,
|
|
320
|
-
usedMessages,
|
|
321
|
-
(o) => `.ezoic-ad-post[data-ezoic-after="${o.after}"][data-ezoic-id="${o.id}"]`,
|
|
322
|
-
lastPost
|
|
323
|
-
);
|
|
324
|
-
if (!id) break;
|
|
325
|
-
}
|
|
213
|
+
const $posts = $('[component="post"][data-pid]');
|
|
214
|
+
if (!$posts.length) return;
|
|
215
|
+
|
|
216
|
+
const newIds = [];
|
|
217
|
+
$posts.each(function (idx) {
|
|
218
|
+
const postNo = idx + 1; // 1-based within loaded set
|
|
219
|
+
if (postNo % interval !== 0) return;
|
|
220
|
+
|
|
221
|
+
// Do not insert after the last post in DOM (to keep infinite scroll sentinel stable)
|
|
222
|
+
if (idx === $posts.length - 1) return;
|
|
326
223
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
wrap.setAttribute('data-ezoic-id', String(id));
|
|
331
|
-
wrap.innerHTML = `<div class="ezoic-ad-message-inner"><div id="ezoic-pub-ad-placeholder-${id}"></div></div>`;
|
|
224
|
+
const $post = $(this);
|
|
225
|
+
const existing = $post.next('.ezoic-ad-topic');
|
|
226
|
+
if (existing.length) return;
|
|
332
227
|
|
|
333
|
-
|
|
228
|
+
let id = pickNextId(pool, usedTopic);
|
|
229
|
+
if (!id) {
|
|
230
|
+
id = recycleTopic($posts);
|
|
231
|
+
if (!id) return;
|
|
232
|
+
}
|
|
233
|
+
usedTopic.add(id);
|
|
234
|
+
fifoTopic.push({ id, afterNo: postNo });
|
|
235
|
+
|
|
236
|
+
insertAfter($post, id, 'topic', postNo).addClass('ezoic-ad-topic');
|
|
237
|
+
newIds.push(id);
|
|
238
|
+
});
|
|
334
239
|
|
|
335
|
-
|
|
336
|
-
usedMessages.add(id);
|
|
337
|
-
fifoMsg.push({ after: afterNo, id });
|
|
338
|
-
newIds.push(id);
|
|
240
|
+
if (newIds.length) callEzoic(newIds);
|
|
339
241
|
}
|
|
340
242
|
|
|
341
|
-
|
|
342
|
-
|
|
243
|
+
function injectInCategory() {
|
|
244
|
+
if (!(settings && (settings.enableBetweenAds === true || settings.enableBetweenAds === 'on'))) return;
|
|
245
|
+
const interval = parseInt(settings.intervalTopics, 10) || 6;
|
|
246
|
+
const pool = parsePool(settings.placeholderIds);
|
|
247
|
+
if (!pool.length) return;
|
|
248
|
+
|
|
249
|
+
const $items = $('li[component="category/topic"]');
|
|
250
|
+
if (!$items.length) return;
|
|
251
|
+
|
|
252
|
+
const newIds = [];
|
|
253
|
+
$items.each(function (idx) {
|
|
254
|
+
const pos = idx + 1;
|
|
255
|
+
if (pos % interval !== 0) return;
|
|
256
|
+
|
|
257
|
+
if (idx === $items.length - 1) return;
|
|
343
258
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
259
|
+
const $li = $(this);
|
|
260
|
+
const existing = $li.next('.ezoic-ad-between');
|
|
261
|
+
if (existing.length) return;
|
|
262
|
+
|
|
263
|
+
let id = pickNextId(pool, usedCat);
|
|
264
|
+
if (!id) {
|
|
265
|
+
id = recycleCat($items);
|
|
266
|
+
if (!id) return;
|
|
267
|
+
}
|
|
268
|
+
usedCat.add(id);
|
|
269
|
+
fifoCat.push({ id, afterPos: pos });
|
|
270
|
+
|
|
271
|
+
insertAfter($li, id, 'between', pos).addClass('ezoic-ad-between');
|
|
272
|
+
newIds.push(id);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
if (newIds.length) callEzoic(newIds);
|
|
349
276
|
}
|
|
350
|
-
inFlight = true;
|
|
351
|
-
rerunRequested = false;
|
|
352
277
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
if (
|
|
278
|
+
function refresh() {
|
|
279
|
+
if (!settings) return;
|
|
280
|
+
if (userExcluded()) return;
|
|
356
281
|
|
|
357
282
|
const key = getPageKey();
|
|
358
|
-
if (
|
|
283
|
+
if (pageKey !== key) {
|
|
359
284
|
pageKey = key;
|
|
360
|
-
|
|
361
|
-
cleanupOnNav();
|
|
285
|
+
cleanupForNewPage();
|
|
362
286
|
}
|
|
363
287
|
|
|
364
|
-
|
|
365
|
-
|
|
288
|
+
setupAutoHeight();
|
|
289
|
+
|
|
290
|
+
if (isTopicPage()) {
|
|
291
|
+
injectInTopic();
|
|
292
|
+
} else if (isCategoryTopicList()) {
|
|
293
|
+
injectInCategory();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
366
296
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
297
|
+
// triggers: hard load + ajaxify + infinite scroll events
|
|
298
|
+
function boot() {
|
|
299
|
+
loadSettings(function () {
|
|
300
|
+
refresh();
|
|
301
|
+
// extra delayed refresh for late ez init
|
|
302
|
+
setTimeout(refresh, 1500);
|
|
303
|
+
setTimeout(refresh, 5000);
|
|
304
|
+
setTimeout(refresh, 10000);
|
|
305
|
+
});
|
|
372
306
|
}
|
|
373
|
-
}
|
|
374
307
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
308
|
+
// Hard load
|
|
309
|
+
$(document).ready(boot);
|
|
310
|
+
// Ajaxify nav end
|
|
311
|
+
$(window).on('action:ajaxify.end', boot);
|
|
312
|
+
// Infinite scroll loads (varies by view)
|
|
313
|
+
$(window).on('action:posts.loaded action:topic.loaded action:topics.loaded action:category.loaded', function () {
|
|
314
|
+
refresh();
|
|
315
|
+
setTimeout(refresh, 800);
|
|
316
|
+
});
|
|
379
317
|
|
|
380
|
-
//
|
|
381
|
-
(function setupTriggers() {
|
|
382
|
-
// Ajaxify navigation: only cleanup when URL changes
|
|
318
|
+
// Clean only on real navigation start (not infinite loads)
|
|
383
319
|
$(window).on('action:ajaxify.start', function (ev, data) {
|
|
384
320
|
try {
|
|
385
|
-
const
|
|
386
|
-
if (
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if (targetPath === window.location.pathname) return;
|
|
391
|
-
}
|
|
321
|
+
const url = data && (data.url || data.href);
|
|
322
|
+
if (!url) return;
|
|
323
|
+
const a = document.createElement('a');
|
|
324
|
+
a.href = url;
|
|
325
|
+
if (a.pathname && a.pathname === window.location.pathname) return;
|
|
392
326
|
} catch (e) {}
|
|
393
327
|
pageKey = null;
|
|
394
|
-
|
|
395
|
-
cleanupOnNav();
|
|
328
|
+
cleanupForNewPage();
|
|
396
329
|
});
|
|
397
330
|
|
|
398
|
-
$(window).on('action:ajaxify.end action:posts.loaded action:topics.loaded action:topic.loaded action:category.loaded', debounceRefresh);
|
|
399
|
-
|
|
400
|
-
// MutationObserver (new posts/topics appended)
|
|
401
|
-
try {
|
|
402
|
-
const obs = new MutationObserver(function (mutations) {
|
|
403
|
-
for (const m of mutations) {
|
|
404
|
-
if (!m.addedNodes) continue;
|
|
405
|
-
for (const n of m.addedNodes) {
|
|
406
|
-
if (!n || n.nodeType !== 1) continue;
|
|
407
|
-
if (n.matches && (n.matches('[component="post"][data-pid]') || n.matches('li[component="category/topic"]'))) {
|
|
408
|
-
debounceRefresh();
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
if (n.querySelector && (n.querySelector('[component="post"][data-pid]') || n.querySelector('li[component="category/topic"]'))) {
|
|
412
|
-
debounceRefresh();
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
});
|
|
418
|
-
obs.observe(document.body, { childList: true, subtree: true });
|
|
419
|
-
window.__ezoicInfiniteObserver = obs;
|
|
420
|
-
} catch (e) {}
|
|
421
|
-
|
|
422
|
-
// Poller fallback (count changes)
|
|
423
|
-
let lastPosts = 0;
|
|
424
|
-
let lastTopics = 0;
|
|
425
|
-
setInterval(function () {
|
|
426
|
-
const p = document.querySelectorAll('[component="post"][data-pid]').length;
|
|
427
|
-
const t = document.querySelectorAll('li[component="category/topic"]').length;
|
|
428
|
-
if (p !== lastPosts || t !== lastTopics) {
|
|
429
|
-
lastPosts = p; lastTopics = t;
|
|
430
|
-
debounceRefresh();
|
|
431
|
-
}
|
|
432
|
-
}, 1500);
|
|
433
|
-
|
|
434
|
-
// First run(s) - important for hard load
|
|
435
|
-
document.addEventListener('DOMContentLoaded', function () {
|
|
436
|
-
debounceRefresh();
|
|
437
|
-
setTimeout(debounceRefresh, 1500);
|
|
438
|
-
setTimeout(debounceRefresh, 5000);
|
|
439
|
-
setTimeout(debounceRefresh, 10000);
|
|
440
|
-
});
|
|
441
|
-
// In case this script loads after DOMContentLoaded
|
|
442
|
-
setTimeout(debounceRefresh, 800);
|
|
443
331
|
})();
|
package/public/style.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
.ezoic-ad{min-height:0 !important;height:auto !important;padding:0 !important;margin:0.5rem 0;}
|
|
2
2
|
.ezoic-ad:not(.ezoic-filled){display:none !important;}
|
|
3
|
-
.ezoic-ad-
|
|
4
|
-
.ezoic-ad-
|
|
3
|
+
.ezoic-ad .ezoic-ad-inner{padding:0;margin:0;}
|
|
4
|
+
.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 Infinite</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
|
+
Placeholders format: <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" for
|
|
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">Exclude groups (ads disabled for members of these groups)</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">Hold Ctrl/Cmd to select multiple. Groups are sorted alphabetically.</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>Between topics in category (topic list)</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">Enable between-topic ads</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">Interval (insert after every 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">Placeholder ID pool (one per line)</label>
|
|
35
|
+
<textarea class="form-control" name="placeholderIds" rows="6"></textarea>
|
|
42
36
|
</div>
|
|
43
37
|
|
|
44
38
|
<hr/>
|
|
45
39
|
|
|
46
|
-
<
|
|
40
|
+
<h3>Inside topics (between posts)</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">Enable between-post ads</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">Interval (insert after every N posts)</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">Message placeholder ID pool (one per line)</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">Save</button>
|
|
58
58
|
</form>
|
|
59
59
|
</div>
|