nodebb-plugin-ezoic-infinite 1.4.92 → 1.4.94

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.4.92",
3
+ "version": "1.4.94",
4
4
  "description": "Production-ready Ezoic infinite ads integration for NodeBB 4.x",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
@@ -8,7 +8,6 @@
8
8
  }, WRAP_CLASS = 'ezoic-ad';
9
9
  const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-', MAX_INSERTS_PER_RUN = 3;
10
10
 
11
- // Nécessaire pour savoir si on doit appeler destroyPlaceholders avant recyclage.
12
11
  const sessionDefinedIds = new Set();
13
12
 
14
13
  const insertingIds = new Set(), state = {
@@ -106,9 +105,7 @@
106
105
 
107
106
  function destroyPlaceholderIds(ids) {
108
107
  if (!ids || !ids.length) return;
109
- // Only destroy ids that were actually "defined" (filled at least once) to avoid Ezoic warnings.
110
108
  const filtered = ids.filter((id) => {
111
- // Utiliser sessionDefinedIds (survit aux navigations) plutôt que state.definedIds
112
109
  try { return sessionDefinedIds.has(id); } catch (e) { return true; }
113
110
  });
114
111
  if (!filtered.length) return;
@@ -119,6 +116,11 @@
119
116
  window.ezstandalone.destroyPlaceholders(filtered);
120
117
  }
121
118
  } catch (e) {}
119
+
120
+ // Recyclage: libérer IDs après 100ms
121
+ setTimeout(() => {
122
+ filtered.forEach(id => sessionDefinedIds.delete(id));
123
+ }, 100);
122
124
  };
123
125
  try {
124
126
  window.ezstandalone = window.ezstandalone || {};
@@ -126,10 +128,50 @@
126
128
  if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') call();
127
129
  else window.ezstandalone.cmd.push(call);
128
130
  } catch (e) {}
131
+
132
+ // Recyclage: libérer IDs après 100ms
133
+ setTimeout(() => {
134
+ filtered.forEach(id => sessionDefinedIds.delete(id));
135
+ }, 100);
129
136
  }
130
137
 
138
+ // Nettoyer éléments Ezoic invisibles qui créent espace vertical
139
+ function cleanupInvisibleEzoicElements() {
140
+ try {
141
+ document.querySelectorAll('.ezoic-ad').forEach(wrapper => {
142
+ const ph = wrapper.querySelector('[id^="ezoic-pub-ad-placeholder-"]');
143
+ if (!ph) return;
144
+
145
+ // Supprimer TOUS les éléments après le placeholder rempli
146
+ // qui créent de l'espace vertical
147
+ let found = false;
148
+ Array.from(wrapper.children).forEach(child => {
149
+ if (child === ph || child.contains(ph)) {
150
+ found = true;
151
+ return;
152
+ }
153
+
154
+ // Si élément APRÈS le placeholder
155
+ if (found) {
156
+ const rect = child.getBoundingClientRect();
157
+ const computed = window.getComputedStyle(child);
131
158
 
132
- // Nettoyer les wrappers vides (sans pub) pour éviter espaces verticaux
159
+ // Supprimer si:
160
+ // 1. Height > 0 mais pas de texte/image visible
161
+ // 2. Ou opacity: 0
162
+ // 3. Ou visibility: hidden
163
+ const hasContent = child.textContent.trim().length > 0 ||
164
+ child.querySelector('img, iframe, video');
165
+
166
+ if (!hasContent || computed.opacity === '0' || computed.visibility === 'hidden') {
167
+ child.remove();
168
+ }
169
+ }
170
+ });
171
+ });
172
+ } catch (e) {}
173
+ }
174
+
133
175
  function cleanupEmptyWrappers() {
134
176
  try {
135
177
  document.querySelectorAll('.ezoic-ad').forEach(wrapper => {
@@ -140,7 +182,7 @@
140
182
  if (ph.children.length === 0) {
141
183
  wrapper.remove();
142
184
  }
143
- }, 3000);
185
+ }, 1500);
144
186
  }
145
187
  });
146
188
  } catch (e) {}
@@ -217,10 +259,8 @@
217
259
  if (findWrap(kindClass, afterPos)) return null;
218
260
 
219
261
  // CRITICAL: Double-lock pour éviter race conditions sur les doublons
220
- // 1. Vérifier qu'aucun autre thread n'est en train d'insérer cet ID
221
262
  if (insertingIds.has(id)) return null;
222
263
 
223
- // 2. Vérifier qu'aucun placeholder avec cet ID n'existe déjà dans le DOM
224
264
  const existingPh = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
225
265
  if (existingPh && existingPh.isConnected) return null;
226
266
 
@@ -233,7 +273,6 @@
233
273
  attachFillObserver(wrap, id);
234
274
  return wrap;
235
275
  } finally {
236
- // Libérer le lock après 100ms (le temps que le DOM soit stable)
237
276
  setTimeout(() => insertingIds.delete(id), 50);
238
277
  }
239
278
  }
@@ -244,8 +283,6 @@
244
283
  }
245
284
 
246
285
  function patchShowAds() {
247
- // Minimal safety net: batch showAds can be triggered by other scripts; split into individual calls.
248
- // Also ensures the patch is applied even if Ezoic loads after our script.
249
286
  const applyPatch = () => {
250
287
  try {
251
288
  window.ezstandalone = window.ezstandalone || {}, ez = window.ezstandalone;
@@ -272,7 +309,6 @@
272
309
  };
273
310
 
274
311
  applyPatch();
275
- // Si Ezoic n'est pas encore chargé, appliquer le patch via sa cmd queue
276
312
  if (!window.__nodebbEzoicPatched) {
277
313
  try {
278
314
  window.ezstandalone = window.ezstandalone || {};
@@ -285,7 +321,6 @@
285
321
  function markFilled(wrap) {
286
322
  try {
287
323
  if (!wrap) return;
288
- // Disconnect the fill observer first (no need to remove+re-add the attribute)
289
324
  if (wrap.__ezoicFillObs) { wrap.__ezoicFillObs.disconnect(); wrap.__ezoicFillObs = null; }
290
325
  wrap.setAttribute('data-ezoic-filled', '1');
291
326
  } catch (e) {}
@@ -301,19 +336,18 @@
301
336
  if (!ph) return;
302
337
  // Already filled?
303
338
  if (ph.childNodes && ph.childNodes.length > 0) {
304
- markFilled(wrap);
305
- state.definedIds && state.definedIds.add(id); sessionDefinedIds.add(id);
339
+ markFilled(wrap); // Afficher wrapper
340
+ sessionDefinedIds.add(id);
306
341
  return;
307
342
  }
308
343
  const obs = new MutationObserver(() => {
309
344
  if (ph.childNodes && ph.childNodes.length > 0) {
310
- markFilled(wrap);
311
- try { state.definedIds && state.definedIds.add(id); sessionDefinedIds.add(id); } catch (e) {}
345
+ markFilled(wrap); // CRITIQUE: Afficher wrapper maintenant
346
+ try { sessionDefinedIds.add(id); } catch (e) {}
312
347
  try { obs.disconnect(); } catch (e) {}
313
348
  }
314
349
  });
315
350
  obs.observe(ph, { childList: true, subtree: true });
316
- // Keep a weak reference on the wrapper so we can disconnect on recycle/remove
317
351
  wrap.__ezoicFillObs = obs;
318
352
  } catch (e) {}
319
353
  }
@@ -333,15 +367,12 @@
333
367
  return filled;
334
368
  }
335
369
 
336
- // Appeler showAds() en batch selon recommandations Ezoic
337
- // Au lieu de showAds(id1), showAds(id2)... faire showAds(id1, id2, id3...)
338
370
  let batchShowAdsTimer = null;
339
371
  const pendingShowAdsIds = new Set();
340
372
 
341
373
  function scheduleShowAdsBatch(id) {
342
374
  if (!id) return;
343
375
 
344
- // CRITIQUE: Si cet ID a déjà été défini (sessionDefinedIds), le détruire d'abord
345
376
  if (sessionDefinedIds.has(id)) {
346
377
  try {
347
378
  destroyPlaceholderIds([id]);
@@ -356,7 +387,6 @@
356
387
  // Ajouter à la batch
357
388
  pendingShowAdsIds.add(id);
358
389
 
359
- // Debounce: attendre 100ms pour collecter tous les IDs
360
390
  clearTimeout(batchShowAdsTimer);
361
391
  batchShowAdsTimer = setTimeout(() => {
362
392
  if (pendingShowAdsIds.size === 0) return;
@@ -380,6 +410,11 @@
380
410
  }
381
411
  });
382
412
  } catch (e) {}
413
+
414
+ // CRITIQUE: Nettoyer éléments invisibles APRÈS que pubs soient chargées
415
+ setTimeout(() => {
416
+ cleanupInvisibleEzoicElements();
417
+ }, 800); // 1.5s pour laisser Ezoic charger
383
418
  }, 100);
384
419
  }
385
420
 
@@ -406,19 +441,15 @@
406
441
  const startPageKey = state.pageKey;
407
442
  let attempts = 0;
408
443
  (function waitForPh() {
409
- // Abort if the user navigated away since this showAds was scheduled
410
444
  if (state.pageKey !== startPageKey) return;
411
- // Abort if another concurrent call is already handling this id
412
445
  if (state.pendingById.has(id)) return;
413
446
 
414
447
  attempts += 1;
415
448
  const el = document.getElementById(phId);
416
449
  if (el && el.isConnected) {
417
- // CRITIQUE: Vérifier que le placeholder est VISIBLE
418
450
 
419
451
  // Si on arrive ici, soit visible, soit timeout
420
452
 
421
- // Si doCall() réussit, Ezoic est chargé et showAds a été appelé → sortir
422
453
  if (doCall()) {
423
454
  state.pendingById.delete(id);
424
455
  return;
@@ -484,7 +515,6 @@
484
515
  const el = items[afterPos - 1];
485
516
  if (!el || !el.isConnected) continue;
486
517
 
487
- // Prevent adjacent ads (DOM-based, robust against virtualization)
488
518
  if (isAdjacentAd(el) || isPrevAd(el)) {
489
519
  continue;
490
520
  }
@@ -501,17 +531,14 @@
501
531
 
502
532
  let wrap = null;
503
533
  if (pick.recycled && pick.recycled.wrap) {
504
- // Only destroy if Ezoic has actually defined this placeholder before
505
534
  if (sessionDefinedIds.has(id)) {
506
535
  destroyPlaceholderIds([id]);
507
536
  }
508
- // Remove the old wrapper entirely, then create a fresh wrapper at the new position (same id)
509
537
  const oldWrap = pick.recycled.wrap;
510
538
  try { if (oldWrap && oldWrap.__ezoicFillObs) { oldWrap.__ezoicFillObs.disconnect(); } } catch (e) {}
511
539
  try { oldWrap && oldWrap.remove(); } catch (e) {}
512
540
  wrap = insertAfter(el, id, kindClass, afterPos);
513
541
  if (!wrap) continue;
514
- // Attendre que le wrapper soit dans le DOM puis appeler showAds
515
542
  setTimeout(() => {
516
543
  callShowAdsWhenReady(id);
517
544
  }, 50);
@@ -525,12 +552,10 @@
525
552
  }
526
553
 
527
554
  liveArr.push({ id, wrap });
528
- // If adjacency ended up happening (e.g. DOM shifts), rollback this placement.
529
555
  if (wrap && (
530
556
  (wrap.previousElementSibling && wrap.previousElementSibling.classList && wrap.previousElementSibling.classList.contains(WRAP_CLASS)) || (wrap.nextElementSibling && wrap.nextElementSibling.classList && wrap.nextElementSibling.classList.contains(WRAP_CLASS))
531
557
  )) {
532
558
  try { wrap.remove(); } catch (e) {}
533
- // Put id back if it was newly consumed (not recycled)
534
559
  if (!(pick.recycled && pick.recycled.wrap)) {
535
560
  try { kindPool.unshift(id); } catch (e) {}
536
561
  usedSet.delete(id);
@@ -547,7 +572,6 @@
547
572
  for (let i = 0; i < ads.length; i++) {
548
573
  const ad = ads[i], prev = ad.previousElementSibling;
549
574
  if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) {
550
- // Supprimer le wrapper adjacent au lieu de le cacher
551
575
  try {
552
576
  const ph = ad.querySelector && ad.querySelector(`[id^="${PLACEHOLDER_PREFIX}"]`);
553
577
  if (ph) {
@@ -568,8 +592,6 @@
568
592
  function cleanup() {
569
593
  destroyUsedPlaceholders();
570
594
 
571
- // CRITIQUE: Supprimer TOUS les wrappers .ezoic-ad du DOM
572
- // Sinon ils restent et deviennent "unused" sur la nouvelle page
573
595
  document.querySelectorAll('.ezoic-ad').forEach(el => {
574
596
  try { el.remove(); } catch (e) {}
575
597
  });
@@ -585,23 +607,14 @@
585
607
  state.usedPosts.clear();
586
608
  state.usedCategories.clear();
587
609
  state.lastShowById.clear();
588
- // CRITIQUE: Vider pendingById pour annuler tous les showAds en cours
589
- // Sinon Ezoic essaie d'accéder aux placeholders pendant que NodeBB vide le DOM
590
610
  state.pendingById.clear();
591
611
  state.definedIds.clear();
592
612
 
593
- // NE PAS supprimer les wrappers Ezoic ici - ils seront supprimés naturellement
594
- // quand NodeBB vide le DOM lors de la navigation ajaxify
595
- // Les supprimer manuellement cause des problèmes avec l'état interne d'Ezoic
596
-
597
- // CRITIQUE: Annuler TOUS les timeouts en cours pour éviter que les anciens
598
- // showAds() continuent à s'exécuter après la navigation
599
613
  state.activeTimeouts.forEach(id => {
600
614
  try { clearTimeout(id); } catch (e) {}
601
615
  });
602
616
  state.activeTimeouts.clear();
603
617
 
604
- // Vider aussi pendingById pour annuler les showAds en attente
605
618
  state.pendingById.clear();
606
619
 
607
620
  if (state.obs) { state.obs.disconnect(); state.obs = null; }
@@ -618,7 +631,6 @@
618
631
  }
619
632
 
620
633
  async function runCore() {
621
- // Attendre que canInsert soit true (protection race condition navigation)
622
634
  if (!state.canShowAds) {
623
635
  return;
624
636
  }
@@ -661,7 +673,6 @@
661
673
 
662
674
  enforceNoAdjacentAds();
663
675
 
664
- // If nothing inserted and list isn't in DOM yet (first click), retry a bit
665
676
  let count = 0;
666
677
  if (kind === 'topic') count = getPostContainers().length;
667
678
  else if (kind === 'categoryTopics') count = getTopicItems().length;
@@ -673,12 +684,9 @@
673
684
  }
674
685
 
675
686
  if (inserted >= MAX_INSERTS_PER_RUN) {
676
- // Plus d'insertions possibles ce cycle, continuer immédiatement
677
687
  setTimeout(arguments[0], 50);
678
688
  } else if (inserted === 0 && count > 0) {
679
689
  // Pool épuisé ou recyclage pas encore disponible.
680
- // Réessayer jusqu'à 8 fois (toutes les 400ms) pour laisser aux anciens wrappers
681
- // le temps de défiler hors écran et devenir recyclables.
682
690
  if (state.poolWaitAttempts < 8) {
683
691
  state.poolWaitAttempts += 1;
684
692
  setTimeout(arguments[0], 50);
@@ -712,15 +720,11 @@
712
720
  state.pageKey = getPageKey();
713
721
  ensureObserver();
714
722
 
715
- // CRITIQUE: Attendre 300ms avant de permettre l'insertion de nouveaux placeholders
716
- // pour laisser les anciens showAds() (en cours) se terminer ou échouer proprement
717
- // Sinon race condition: NodeBB vide le DOM pendant que Ezoic essaie d'accéder aux placeholders
718
723
  state.canShowAds = true;
719
724
  });
720
725
 
721
726
  $(window).on('action:category.loaded.ezoicInfinite', () => {
722
727
  ensureObserver();
723
- // category.loaded = infinite scroll, Ezoic déjà chargé normalement
724
728
  waitForContentThenRun();
725
729
  });
726
730
  $(window).on('action:topics.loaded.ezoicInfinite', () => {
@@ -750,7 +754,6 @@
750
754
  window.requestAnimationFrame(() => {
751
755
  ticking = false;
752
756
  enforceNoAdjacentAds();
753
- // Debounce scheduleRun - une fois toutes les 2 secondes max au scroll
754
757
  const now = Date.now();
755
758
  if (!state.lastScrollRun || now - state.lastScrollRun > 2000) {
756
759
  state.lastScrollRun = now;
@@ -760,7 +763,6 @@
760
763
  }, { passive: true });
761
764
  }
762
765
 
763
- // Fonction qui attend que la page ait assez de contenu avant d'insérer les pubs
764
766
  function waitForContentThenRun() {
765
767
  const MIN_WORDS = 250;
766
768
  let attempts = 0;
@@ -791,7 +793,6 @@
791
793
  })();
792
794
  }
793
795
 
794
- // Fonction qui attend que Ezoic soit vraiment chargé
795
796
  function waitForEzoicThenRun() {
796
797
  let attempts = 0;
797
798
  const maxAttempts = 50; // 50 × 200ms = 10s max
@@ -0,0 +1,10 @@
1
+ .ezoic-ad {
2
+ height: auto !important;
3
+ padding: 0 !important;
4
+ margin: 0 !important;
5
+ }
6
+
7
+ .ezoic-ad * {
8
+ margin: 0 !important;
9
+ padding: 0 !important;
10
+ }
package/public/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/public/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;
@@ -1,21 +0,0 @@
1
- {
2
- "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.4.8",
4
- "description": "Production-ready Ezoic infinite ads integration for NodeBB 4.x",
5
- "main": "library.js",
6
- "license": "MIT",
7
- "keywords": [
8
- "nodebb",
9
- "nodebb-plugin",
10
- "ezoic",
11
- "ads",
12
- "infinite-scroll"
13
- ],
14
- "engines": {
15
- "nodebb": ">=4.0.0"
16
- },
17
- "nbbpm": {
18
- "compatibility": "^4.0.0"
19
- },
20
- "private": false
21
- }
@@ -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
- }
@@ -1,3 +0,0 @@
1
- .ezoic-ad{height:auto !important; padding:0 !important; margin: 0.25rem 0;}
2
- .ezoic-ad .ezoic-ad-inner{padding:0;margin:0;}
3
- .ezoic-ad .ezoic-ad-inner > div{padding:0;margin:0;}
File without changes