nodebb-plugin-ezoic-infinite 1.4.72 → 1.4.73
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/{public/client.js → client.js} +48 -30
- package/package.json +1 -1
- package/style.css +43 -0
- package/README.md +0 -15
- package/library.js +0 -130
- package/plugin.json +0 -33
- package/public/style.css +0 -31
- /package/{public/admin.js → admin.js} +0 -0
- /package/{public/templates → templates}/admin/plugins/ezoic-infinite.tpl +0 -0
|
@@ -318,34 +318,33 @@
|
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
-
|
|
322
|
-
function forcePlaceholderAutoHeight(wrap, id) {
|
|
321
|
+
function markFilled(wrap) {
|
|
323
322
|
try {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
// Neutraliser les hauteurs réservées (inline ou CSS) qui créent un espace après la pub
|
|
328
|
-
ph.style.setProperty('height', 'auto', 'important');
|
|
329
|
-
ph.style.setProperty('min-height', '0px', 'important');
|
|
330
|
-
// Eviter le gap baseline sous les iframes/ins
|
|
331
|
-
wrap.querySelectorAll && wrap.querySelectorAll('iframe, ins').forEach(n => {
|
|
332
|
-
try { n.style.setProperty('display', 'block', 'important'); } catch (e) {}
|
|
333
|
-
});
|
|
334
|
-
requestAnimationFrame(() => {
|
|
335
|
-
try {
|
|
336
|
-
ph.style.setProperty('height', 'auto', 'important');
|
|
337
|
-
ph.style.setProperty('min-height', '0px', 'important');
|
|
338
|
-
} catch (e) {}
|
|
339
|
-
});
|
|
323
|
+
if (!wrap) return;
|
|
324
|
+
if (wrap.__ezoicFillObs) { wrap.__ezoicFillObs.disconnect(); wrap.__ezoicFillObs = null; }
|
|
325
|
+
wrap.setAttribute('data-ezoic-filled', '1');
|
|
340
326
|
} catch (e) {}
|
|
341
327
|
}
|
|
342
328
|
|
|
343
|
-
function
|
|
329
|
+
function forcePlaceholderAutoHeight(wrap, id) {
|
|
344
330
|
try {
|
|
345
|
-
if (!wrap) return;
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
331
|
+
if (!wrap || !id) return;
|
|
332
|
+
const ph = wrap.querySelector && wrap.querySelector(`#${PLACEHOLDER_PREFIX}${id}`);
|
|
333
|
+
if (!ph) return;
|
|
334
|
+
// Override any reserved height/min-height injected by ad scripts
|
|
335
|
+
try {
|
|
336
|
+
ph.style.setProperty('height', 'auto', 'important');
|
|
337
|
+
ph.style.setProperty('min-height', '0px', 'important');
|
|
338
|
+
} catch (e) {}
|
|
339
|
+
// Some creatives adjust after a frame
|
|
340
|
+
try {
|
|
341
|
+
requestAnimationFrame(() => {
|
|
342
|
+
try {
|
|
343
|
+
ph.style.setProperty('height', 'auto', 'important');
|
|
344
|
+
ph.style.setProperty('min-height', '0px', 'important');
|
|
345
|
+
} catch (e) {}
|
|
346
|
+
});
|
|
347
|
+
} catch (e) {}
|
|
349
348
|
} catch (e) {}
|
|
350
349
|
}
|
|
351
350
|
|
|
@@ -359,13 +358,15 @@ function markFilled(wrap, id) {
|
|
|
359
358
|
if (!ph) return;
|
|
360
359
|
// Already filled?
|
|
361
360
|
if (ph.childNodes && ph.childNodes.length > 0) {
|
|
362
|
-
markFilled(wrap
|
|
361
|
+
markFilled(wrap); // Afficher wrapper
|
|
362
|
+
forcePlaceholderAutoHeight(wrap, id);
|
|
363
363
|
sessionDefinedIds.add(id);
|
|
364
364
|
return;
|
|
365
365
|
}
|
|
366
366
|
const obs = new MutationObserver(() => {
|
|
367
367
|
if (ph.childNodes && ph.childNodes.length > 0) {
|
|
368
|
-
markFilled(wrap
|
|
368
|
+
markFilled(wrap); // CRITIQUE: Afficher wrapper maintenant
|
|
369
|
+
forcePlaceholderAutoHeight(wrap, id);
|
|
369
370
|
try { sessionDefinedIds.add(id); } catch (e) {}
|
|
370
371
|
try { obs.disconnect(); } catch (e) {}
|
|
371
372
|
}
|
|
@@ -385,7 +386,7 @@ function markFilled(wrap, id) {
|
|
|
385
386
|
const filled = !!(ph.childNodes && ph.childNodes.length > 0);
|
|
386
387
|
if (filled) {
|
|
387
388
|
try { state.definedIds && state.definedIds.add(id); sessionDefinedIds.add(id); } catch (e) {}
|
|
388
|
-
try { markFilled(wrap
|
|
389
|
+
try { markFilled(wrap); } catch (e) {}
|
|
389
390
|
}
|
|
390
391
|
return filled;
|
|
391
392
|
}
|
|
@@ -414,18 +415,23 @@ function markFilled(wrap, id) {
|
|
|
414
415
|
batchShowAdsTimer = setTimeout(() => {
|
|
415
416
|
if (pendingShowAdsIds.size === 0) return;
|
|
416
417
|
|
|
417
|
-
|
|
418
|
+
// Only keep IDs whose placeholders still exist at execution time
|
|
419
|
+
const idsArray = Array.from(pendingShowAdsIds).filter((id) => {
|
|
420
|
+
const el = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
|
|
421
|
+
return !!(el && el.isConnected);
|
|
422
|
+
});
|
|
418
423
|
pendingShowAdsIds.clear();
|
|
419
424
|
|
|
425
|
+
if (!idsArray.length) return;
|
|
426
|
+
|
|
420
427
|
// Appeler showAds avec TOUS les IDs en une fois
|
|
421
428
|
try {
|
|
422
429
|
window.ezstandalone = window.ezstandalone || {};
|
|
423
430
|
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
424
431
|
window.ezstandalone.cmd.push(function() {
|
|
425
432
|
if (typeof window.ezstandalone.showAds === 'function') {
|
|
426
|
-
//
|
|
427
|
-
|
|
428
|
-
if (okIds.length) { window.ezstandalone.showAds(...okIds); }
|
|
433
|
+
// Call as array (most compatible with ezstandalone)
|
|
434
|
+
window.ezstandalone.showAds(idsArray);
|
|
429
435
|
// Tracker tous les IDs
|
|
430
436
|
idsArray.forEach(id => {
|
|
431
437
|
state.lastShowById.set(id, Date.now());
|
|
@@ -574,6 +580,18 @@ function markFilled(wrap, id) {
|
|
|
574
580
|
// Appel immédiat au lieu de 10ms delay
|
|
575
581
|
callShowAdsWhenReady(id);
|
|
576
582
|
}
|
|
583
|
+
|
|
584
|
+
liveArr.push({ id, wrap });
|
|
585
|
+
if (wrap && (
|
|
586
|
+
(wrap.previousElementSibling && wrap.previousElementSibling.classList && wrap.previousElementSibling.classList.contains(WRAP_CLASS)) || (wrap.nextElementSibling && wrap.nextElementSibling.classList && wrap.nextElementSibling.classList.contains(WRAP_CLASS))
|
|
587
|
+
)) {
|
|
588
|
+
try { wrap.remove(); } catch (e) {}
|
|
589
|
+
if (!(pick.recycled && pick.recycled.wrap)) {
|
|
590
|
+
try { kindPool.unshift(id); } catch (e) {}
|
|
591
|
+
usedSet.delete(id);
|
|
592
|
+
}
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
577
595
|
inserted += 1;
|
|
578
596
|
}
|
|
579
597
|
return inserted;
|
package/package.json
CHANGED
package/style.css
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
NodeBB + Ezoic (standalone)
|
|
3
|
+
Goal: never reserve space until the ad is truly filled, and never keep a fixed/min height.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* Hide wrappers until we detect the placeholder has content (prevents empty gaps if CMP/ads are blocked) */
|
|
7
|
+
.ezoic-ad {
|
|
8
|
+
display: none;
|
|
9
|
+
width: 100%;
|
|
10
|
+
height: auto !important;
|
|
11
|
+
min-height: 0 !important;
|
|
12
|
+
padding: 0 !important;
|
|
13
|
+
margin: 0 !important;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.ezoic-ad[data-ezoic-filled="1"] {
|
|
17
|
+
display: block;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* The placeholder must not reserve space */
|
|
21
|
+
.ezoic-ad > [id^="ezoic-pub-ad-placeholder-"] {
|
|
22
|
+
height: auto !important;
|
|
23
|
+
min-height: 0 !important;
|
|
24
|
+
margin: 0 !important;
|
|
25
|
+
padding: 0 !important;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Avoid baseline gap under iframes/ins */
|
|
29
|
+
.ezoic-ad iframe,
|
|
30
|
+
.ezoic-ad ins {
|
|
31
|
+
display: block !important;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Remove empty spacer divs that sometimes get injected */
|
|
35
|
+
.ezoic-ad > div:empty {
|
|
36
|
+
display: none !important;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Keep internal margins/paddings from creating vertical gaps */
|
|
40
|
+
.ezoic-ad * {
|
|
41
|
+
margin: 0 !important;
|
|
42
|
+
padding: 0 !important;
|
|
43
|
+
}
|
package/README.md
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# NodeBB Plugin – Ezoic Infinite (Production)
|
|
2
|
-
|
|
3
|
-
This plugin injects Ezoic placeholders between topics and posts on NodeBB 4.x,
|
|
4
|
-
with full support for infinite scroll.
|
|
5
|
-
|
|
6
|
-
## Key guarantees
|
|
7
|
-
- No duplicate ads back-to-back
|
|
8
|
-
- One showAds call per placeholder
|
|
9
|
-
- Fast reveal (MutationObserver on first child)
|
|
10
|
-
- Safe with ajaxify navigation
|
|
11
|
-
- Works with NodeBB 4.x + Harmony
|
|
12
|
-
|
|
13
|
-
## Notes
|
|
14
|
-
- Placeholders must exist and be selected in Ezoic
|
|
15
|
-
- Use separate ID pools for topics vs messages
|
package/library.js
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const meta = require.main.require('./src/meta');
|
|
4
|
-
const groups = require.main.require('./src/groups');
|
|
5
|
-
const db = require.main.require('./src/database');
|
|
6
|
-
|
|
7
|
-
const SETTINGS_KEY = 'ezoic-infinite';
|
|
8
|
-
const plugin = {};
|
|
9
|
-
|
|
10
|
-
function normalizeExcludedGroups(value) {
|
|
11
|
-
if (!value) return [];
|
|
12
|
-
if (Array.isArray(value)) return value;
|
|
13
|
-
return String(value).split(',').map(s => s.trim()).filter(Boolean);
|
|
14
|
-
}
|
|
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
|
-
let names = await db.getSortedSetRange('groups:createtime', 0, -1);
|
|
25
|
-
if (!names || !names.length) {
|
|
26
|
-
names = await db.getSortedSetRange('groups:visible:createtime', 0, -1);
|
|
27
|
-
}
|
|
28
|
-
const filtered = names.filter(name => !groups.isPrivilegeGroup(name));
|
|
29
|
-
const data = await groups.getGroupsData(filtered);
|
|
30
|
-
// Filter out nulls (groups deleted between the sorted-set read and getGroupsData)
|
|
31
|
-
const valid = data.filter(g => g && g.name);
|
|
32
|
-
valid.sort((a, b) => String(a.name).localeCompare(String(b.name), undefined, { sensitivity: 'base' }));
|
|
33
|
-
return valid;
|
|
34
|
-
}
|
|
35
|
-
let _settingsCache = null;
|
|
36
|
-
let _settingsCacheAt = 0;
|
|
37
|
-
const SETTINGS_TTL = 30000; // 30s
|
|
38
|
-
|
|
39
|
-
async function getSettings() {
|
|
40
|
-
const now = Date.now();
|
|
41
|
-
if (_settingsCache && (now - _settingsCacheAt) < SETTINGS_TTL) return _settingsCache;
|
|
42
|
-
const s = await meta.settings.get(SETTINGS_KEY);
|
|
43
|
-
_settingsCacheAt = Date.now();
|
|
44
|
-
_settingsCache = {
|
|
45
|
-
// Between-post ads (simple blocks) in category topic list
|
|
46
|
-
enableBetweenAds: parseBool(s.enableBetweenAds, true),
|
|
47
|
-
showFirstTopicAd: parseBool(s.showFirstTopicAd, false),
|
|
48
|
-
placeholderIds: (s.placeholderIds || '').trim(),
|
|
49
|
-
intervalPosts: Math.max(1, parseInt(s.intervalPosts, 10) || 6),
|
|
50
|
-
|
|
51
|
-
// Home/categories list ads (between categories on / or /categories)
|
|
52
|
-
enableCategoryAds: parseBool(s.enableCategoryAds, false),
|
|
53
|
-
showFirstCategoryAd: parseBool(s.showFirstCategoryAd, false),
|
|
54
|
-
categoryPlaceholderIds: (s.categoryPlaceholderIds || '').trim(),
|
|
55
|
-
intervalCategories: Math.max(1, parseInt(s.intervalCategories, 10) || 4),
|
|
56
|
-
|
|
57
|
-
// "Ad message" between replies (looks like a post)
|
|
58
|
-
enableMessageAds: parseBool(s.enableMessageAds, false),
|
|
59
|
-
showFirstMessageAd: parseBool(s.showFirstMessageAd, false),
|
|
60
|
-
messagePlaceholderIds: (s.messagePlaceholderIds || '').trim(),
|
|
61
|
-
messageIntervalPosts: Math.max(1, parseInt(s.messageIntervalPosts, 10) || 3),
|
|
62
|
-
|
|
63
|
-
excludedGroups: normalizeExcludedGroups(s.excludedGroups),
|
|
64
|
-
};
|
|
65
|
-
return _settingsCache;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async function isUserExcluded(uid, excludedGroups) {
|
|
69
|
-
if (!uid || !excludedGroups.length) return false;
|
|
70
|
-
const userGroups = await groups.getUserGroups([uid]);
|
|
71
|
-
return (userGroups[0] || []).some(g => excludedGroups.includes(g.name));
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
plugin.onSettingsSet = function (data) {
|
|
75
|
-
// Invalider le cache dès que les settings de ce plugin sont sauvegardés via l'ACP
|
|
76
|
-
if (data && data.hash === SETTINGS_KEY) {
|
|
77
|
-
_settingsCache = null;
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
plugin.addAdminNavigation = async (header) => {
|
|
82
|
-
header.plugins = header.plugins || [];
|
|
83
|
-
header.plugins.push({
|
|
84
|
-
route: '/plugins/ezoic-infinite',
|
|
85
|
-
icon: 'fa-ad',
|
|
86
|
-
name: 'Ezoic Infinite Ads'
|
|
87
|
-
});
|
|
88
|
-
return header;
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
plugin.init = async ({ router, middleware }) => {
|
|
92
|
-
async function render(req, res) {
|
|
93
|
-
const settings = await getSettings();
|
|
94
|
-
const allGroups = await getAllGroups();
|
|
95
|
-
|
|
96
|
-
res.render('admin/plugins/ezoic-infinite', {
|
|
97
|
-
title: 'Ezoic Infinite Ads',
|
|
98
|
-
...settings,
|
|
99
|
-
enableBetweenAds_checked: settings.enableBetweenAds ? 'checked' : '',
|
|
100
|
-
enableMessageAds_checked: settings.enableMessageAds ? 'checked' : '',
|
|
101
|
-
allGroups,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
router.get('/admin/plugins/ezoic-infinite', middleware.admin.buildHeader, render);
|
|
106
|
-
router.get('/api/admin/plugins/ezoic-infinite', render);
|
|
107
|
-
|
|
108
|
-
router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
|
|
109
|
-
const settings = await getSettings();
|
|
110
|
-
const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
|
|
111
|
-
|
|
112
|
-
res.json({
|
|
113
|
-
excluded,
|
|
114
|
-
enableBetweenAds: settings.enableBetweenAds,
|
|
115
|
-
showFirstTopicAd: settings.showFirstTopicAd,
|
|
116
|
-
placeholderIds: settings.placeholderIds,
|
|
117
|
-
intervalPosts: settings.intervalPosts,
|
|
118
|
-
enableCategoryAds: settings.enableCategoryAds,
|
|
119
|
-
showFirstCategoryAd: settings.showFirstCategoryAd,
|
|
120
|
-
categoryPlaceholderIds: settings.categoryPlaceholderIds,
|
|
121
|
-
intervalCategories: settings.intervalCategories,
|
|
122
|
-
enableMessageAds: settings.enableMessageAds,
|
|
123
|
-
showFirstMessageAd: settings.showFirstMessageAd,
|
|
124
|
-
messagePlaceholderIds: settings.messagePlaceholderIds,
|
|
125
|
-
messageIntervalPosts: settings.messageIntervalPosts,
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
module.exports = plugin;
|
package/plugin.json
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "nodebb-plugin-ezoic-infinite",
|
|
3
|
-
"name": "NodeBB Ezoic Infinite Ads",
|
|
4
|
-
"description": "Ezoic ads with infinite scroll using a pool of placeholder IDs",
|
|
5
|
-
"library": "./library.js",
|
|
6
|
-
"hooks": [
|
|
7
|
-
{
|
|
8
|
-
"hook": "static:app.load",
|
|
9
|
-
"method": "init"
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
"hook": "filter:admin.header.build",
|
|
13
|
-
"method": "addAdminNavigation"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"hook": "action:settings.set",
|
|
17
|
-
"method": "onSettingsSet"
|
|
18
|
-
}
|
|
19
|
-
],
|
|
20
|
-
"staticDirs": {
|
|
21
|
-
"public": "public"
|
|
22
|
-
},
|
|
23
|
-
"acpScripts": [
|
|
24
|
-
"public/admin.js"
|
|
25
|
-
],
|
|
26
|
-
"scripts": [
|
|
27
|
-
"public/client.js"
|
|
28
|
-
],
|
|
29
|
-
"templates": "public/templates",
|
|
30
|
-
"css": [
|
|
31
|
-
"public/style.css"
|
|
32
|
-
]
|
|
33
|
-
}
|
package/public/style.css
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
/* Wrapper: invisible tant que la pub n'est pas réellement insérée */
|
|
3
|
-
.ezoic-ad{
|
|
4
|
-
display:none;
|
|
5
|
-
width:100%;
|
|
6
|
-
height:auto !important;
|
|
7
|
-
padding:0 !important;
|
|
8
|
-
margin:0 !important;
|
|
9
|
-
}
|
|
10
|
-
.ezoic-ad[data-ezoic-filled="1"]{
|
|
11
|
-
display:block;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/* Le placeholder ne doit jamais réserver une hauteur fixe */
|
|
15
|
-
.ezoic-ad > [id^="ezoic-pub-ad-placeholder-"]{
|
|
16
|
-
height:auto !important;
|
|
17
|
-
min-height:0 !important;
|
|
18
|
-
margin:0 !important;
|
|
19
|
-
padding:0 !important;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/* Évite le gap baseline sous iframes/ins */
|
|
23
|
-
.ezoic-ad iframe,
|
|
24
|
-
.ezoic-ad ins{
|
|
25
|
-
display:block !important;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/* Supprimer les spacers vides */
|
|
29
|
-
.ezoic-ad > div:empty{
|
|
30
|
-
display:none !important;
|
|
31
|
-
}
|
|
File without changes
|
|
File without changes
|