nodebb-plugin-ezoic-infinite 1.2.6 → 1.4.0

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 CHANGED
@@ -34,13 +34,21 @@ async function getAllGroups() {
34
34
  async function getSettings() {
35
35
  const s = await meta.settings.get(SETTINGS_KEY);
36
36
  return {
37
- // Between-post ads (simple blocks)
37
+ // Between-post ads (simple blocks) in category topic list
38
38
  enableBetweenAds: parseBool(s.enableBetweenAds, true),
39
+ showFirstTopicAd: parseBool(s.showFirstTopicAd, false),
39
40
  placeholderIds: (s.placeholderIds || '').trim(),
40
41
  intervalPosts: Math.max(1, parseInt(s.intervalPosts, 10) || 6),
41
42
 
43
+ // Home/categories list ads (between categories on / or /categories)
44
+ enableCategoryAds: parseBool(s.enableCategoryAds, false),
45
+ showFirstCategoryAd: parseBool(s.showFirstCategoryAd, false),
46
+ categoryPlaceholderIds: (s.categoryPlaceholderIds || '').trim(),
47
+ intervalCategories: Math.max(1, parseInt(s.intervalCategories, 10) || 4),
48
+
42
49
  // "Ad message" between replies (looks like a post)
43
50
  enableMessageAds: parseBool(s.enableMessageAds, false),
51
+ showFirstMessageAd: parseBool(s.showFirstMessageAd, false),
44
52
  messagePlaceholderIds: (s.messagePlaceholderIds || '').trim(),
45
53
  messageIntervalPosts: Math.max(1, parseInt(s.messageIntervalPosts, 10) || 3),
46
54
 
@@ -88,9 +96,15 @@ plugin.init = async ({ router, middleware }) => {
88
96
  res.json({
89
97
  excluded,
90
98
  enableBetweenAds: settings.enableBetweenAds,
99
+ showFirstTopicAd: settings.showFirstTopicAd,
91
100
  placeholderIds: settings.placeholderIds,
92
101
  intervalPosts: settings.intervalPosts,
102
+ enableCategoryAds: settings.enableCategoryAds,
103
+ showFirstCategoryAd: settings.showFirstCategoryAd,
104
+ categoryPlaceholderIds: settings.categoryPlaceholderIds,
105
+ intervalCategories: settings.intervalCategories,
93
106
  enableMessageAds: settings.enableMessageAds,
107
+ showFirstMessageAd: settings.showFirstMessageAd,
94
108
  messagePlaceholderIds: settings.messagePlaceholderIds,
95
109
  messageIntervalPosts: settings.messageIntervalPosts,
96
110
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.2.6",
3
+ "version": "1.4.0",
4
4
  "description": "Production-ready Ezoic infinite ads integration for NodeBB 4.x",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
package/public/client.js CHANGED
@@ -1,21 +1,17 @@
1
- /* eslint-disable no-console */
2
1
  (function () {
3
2
  'use strict';
4
3
 
5
- // Optional debug switch
6
- const DEBUG = !!window.__ezoicInfiniteDebug;
7
-
8
4
  const $ = (typeof window.jQuery === 'function') ? window.jQuery : null;
9
5
 
10
6
  const SELECTORS = {
11
7
  topicItem: 'li[component="category/topic"]',
12
8
  postItem: '[component="post"][data-pid]',
9
+ categoryItem: 'li[component="categories/category"]',
13
10
  };
14
11
 
15
12
  const WRAP_CLASS = 'ezoic-ad';
16
13
  const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-';
17
14
 
18
- // Prevent “burst” injection: at most N inserts per run per kind
19
15
  const MAX_INSERTS_PER_RUN = 2;
20
16
 
21
17
  const state = {
@@ -23,33 +19,24 @@
23
19
  cfg: null,
24
20
  cfgPromise: null,
25
21
 
26
- // Per-page pools (refilled by recycling)
27
22
  poolTopics: [],
28
23
  poolPosts: [],
24
+ poolCategories: [],
29
25
 
30
- // Track inserted ads per page
31
26
  usedTopics: new Set(),
32
27
  usedPosts: new Set(),
28
+ usedCategories: new Set(),
33
29
 
34
- // Track which anchors we already evaluated to avoid reprocessing everything on each event
35
- seenTopicAnchors: new WeakSet(),
36
- seenPostAnchors: new WeakSet(),
37
-
38
- // showAds anti-double
39
30
  lastShowById: new Map(),
40
31
  pendingById: new Set(),
41
32
 
42
- // debounce
43
33
  scheduled: false,
44
34
  timer: null,
45
35
 
46
- // observers
47
36
  obs: null,
37
+ attempts: 0,
48
38
  };
49
39
 
50
- function log(...args) { if (DEBUG) console.log('[ezoic-infinite]', ...args); }
51
- function warn(...args) { if (DEBUG) console.warn('[ezoic-infinite]', ...args); }
52
-
53
40
  function normalizeBool(v) {
54
41
  return v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
55
42
  }
@@ -87,89 +74,87 @@
87
74
  function getKind() {
88
75
  const p = window.location.pathname || '';
89
76
  if (/^\/topic\//.test(p)) return 'topic';
90
- if (/^\/category\//.test(p)) return 'category';
91
- // fallback
77
+ if (/^\/category\//.test(p)) return 'categoryTopics';
78
+ if (p === '/' || /^\/categories/.test(p)) return 'categories';
79
+ // fallback by DOM
80
+ if (document.querySelector(SELECTORS.categoryItem)) return 'categories';
92
81
  if (document.querySelector(SELECTORS.postItem)) return 'topic';
93
- return 'category';
82
+ if (document.querySelector(SELECTORS.topicItem)) return 'categoryTopics';
83
+ return 'other';
94
84
  }
95
85
 
96
-
97
86
  function getTopicItems() {
98
87
  return Array.from(document.querySelectorAll(SELECTORS.topicItem));
99
88
  }
100
89
 
90
+ function getCategoryItems() {
91
+ return Array.from(document.querySelectorAll(SELECTORS.categoryItem));
92
+ }
93
+
101
94
  function getPostContainers() {
102
- // Harmony can include multiple [component="post"] blocks (e.g. parent previews, nested structures).
103
- // We only want top-level post containers that actually contain post content.
104
95
  const nodes = Array.from(document.querySelectorAll(SELECTORS.postItem));
105
96
  return nodes.filter((el) => {
106
97
  if (!el || !el.isConnected) return false;
107
-
108
- // Must contain post content
109
98
  if (!el.querySelector('[component="post/content"]')) return false;
110
-
111
- // Must not be nested within another post container
112
99
  const parentPost = el.parentElement && el.parentElement.closest('[component="post"][data-pid]');
113
100
  if (parentPost && parentPost !== el) return false;
114
-
115
- // Avoid "parent/quote" blocks that use component="post/parent"
116
101
  if (el.getAttribute('component') === 'post/parent') return false;
117
-
118
102
  return true;
119
103
  });
120
104
  }
121
105
 
122
- function hasAdImmediatelyAfter(el) {
123
- const n = el && el.nextElementSibling;
124
- return !!(n && n.classList && n.classList.contains(WRAP_CLASS));
125
- }
126
-
127
- function enforceNoAdjacentAds() {
128
- // NodeBB can virtualize (remove) topics/posts from the DOM while keeping our ad wrappers,
129
- // which can temporarily make two ad wrappers adjacent. Hide the later one to avoid back-to-back ads.
130
- const ads = Array.from(document.querySelectorAll(`.${WRAP_CLASS}`));
131
- for (let i = 0; i < ads.length; i++) {
132
- const ad = ads[i];
133
- const prev = ad.previousElementSibling;
134
- if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) {
135
- ad.style.display = 'none';
136
- } else {
137
- ad.style.display = '';
138
- }
139
- }
140
- }
141
-
142
-
143
- const n = el && el.nextElementSibling;
144
- return !!(n && n.classList && n.classList.contains(WRAP_CLASS));
145
- }
146
-
147
- function buildWrap(id, kind, afterPos) {
106
+ function buildWrap(id, kindClass, afterPos) {
148
107
  const wrap = document.createElement('div');
149
- wrap.className = `${WRAP_CLASS} ${kind}`;
108
+ wrap.className = `${WRAP_CLASS} ${kindClass}`;
150
109
  wrap.setAttribute('data-ezoic-after', String(afterPos));
151
110
  wrap.style.width = '100%';
111
+
152
112
  const ph = document.createElement('div');
153
113
  ph.id = `${PLACEHOLDER_PREFIX}${id}`;
154
114
  wrap.appendChild(ph);
155
115
  return wrap;
156
116
  }
157
117
 
158
- function insertAfter(target, id, kind, afterPos) {
118
+ function findWrap(kindClass, afterPos) {
119
+ return document.querySelector(`.${WRAP_CLASS}.${kindClass}[data-ezoic-after="${afterPos}"]`);
120
+ }
121
+
122
+ function insertAfter(target, id, kindClass, afterPos) {
159
123
  if (!target || !target.insertAdjacentElement) return null;
160
- const wrap = buildWrap(id, kind, afterPos);
124
+ if (findWrap(kindClass, afterPos)) return null;
125
+ const wrap = buildWrap(id, kindClass, afterPos);
161
126
  target.insertAdjacentElement('afterend', wrap);
162
127
  return wrap;
163
128
  }
164
129
 
165
- function safeRect(el) {
166
- try { return el.getBoundingClientRect(); } catch (e) { return null; }
167
- }
130
+ function destroyUsedPlaceholders() {
131
+ const ids = [];
132
+ try {
133
+ state.usedTopics.forEach((id) => ids.push(id));
134
+ state.usedPosts.forEach((id) => ids.push(id));
135
+ state.usedCategories.forEach((id) => ids.push(id));
136
+ } catch (e) {}
137
+
138
+ if (!ids.length) return;
139
+
140
+ const call = () => {
141
+ try {
142
+ if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') {
143
+ window.ezstandalone.destroyPlaceholders(ids);
144
+ }
145
+ } catch (e) {}
146
+ };
147
+
148
+ try {
149
+ window.ezstandalone = window.ezstandalone || {};
150
+ window.ezstandalone.cmd = window.ezstandalone.cmd || [];
151
+ if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') call();
152
+ else window.ezstandalone.cmd.push(call);
168
153
  } catch (e) {}
169
154
  }
170
155
 
171
- // Patch ezstandalone.showAds to split batch calls (we NEVER call batch from this plugin)
172
156
  function patchShowAds() {
157
+ // Minimal safety net: batch showAds can be triggered by other scripts; split into individual calls.
173
158
  try {
174
159
  window.ezstandalone = window.ezstandalone || {};
175
160
  const ez = window.ezstandalone;
@@ -179,11 +164,13 @@
179
164
  ez.__nodebbEzoicPatched = true;
180
165
  const orig = ez.showAds;
181
166
 
182
- ez.showAds = function patchedShowAds(arg) {
167
+ ez.showAds = function (arg) {
183
168
  if (Array.isArray(arg)) {
184
- try { warn('showAds(batch) detected. Splitting…'); } catch (e) {}
185
- const ids = uniqInts(arg);
186
- for (const id of ids) {
169
+ const seen = new Set();
170
+ for (const v of arg) {
171
+ const id = parseInt(v, 10);
172
+ if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
173
+ seen.add(id);
187
174
  try { orig.call(ez, id); } catch (e) {}
188
175
  }
189
176
  return;
@@ -236,7 +223,6 @@
236
223
  } catch (e) {}
237
224
  });
238
225
 
239
- // short retries
240
226
  let tries = 0;
241
227
  (function tick() {
242
228
  tries += 1;
@@ -249,17 +235,14 @@
249
235
  return;
250
236
  }
251
237
 
252
- if (attempts < 40) setTimeout(waitForPh, 50);
238
+ if (attempts < 50) setTimeout(waitForPh, 50);
253
239
  })();
254
240
  }
255
- }
256
- return false;
257
- }
258
241
 
259
242
  function nextId(kind) {
260
- const pool = (kind === 'between') ? state.poolTopics : state.poolPosts;
243
+ const pool = (kind === 'between') ? state.poolTopics : (kind === 'message') ? state.poolPosts : state.poolCategories;
261
244
  if (pool.length) return pool.shift();
262
- return null; // stop injecting when pool is empty
245
+ return null;
263
246
  }
264
247
 
265
248
  async function fetchConfig() {
@@ -283,139 +266,80 @@
283
266
  }
284
267
 
285
268
  function initPools(cfg) {
286
- // Re-init pools once per page
287
269
  if (state.poolTopics.length === 0) state.poolTopics = parsePool(cfg.placeholderIds);
288
270
  if (state.poolPosts.length === 0) state.poolPosts = parsePool(cfg.messagePlaceholderIds);
271
+ if (state.poolCategories.length === 0) state.poolCategories = parsePool(cfg.categoryPlaceholderIds);
289
272
  }
290
273
 
291
- function injectTopics(cfg) {
292
- if (!normalizeBool(cfg.enableBetweenAds)) return 0;
293
-
294
- const interval = Math.max(1, parseInt(cfg.intervalPosts, 10) || 6);
295
- const first = normalizeBool(cfg.showFirstTopicAd);
296
-
297
- const items = getTopicItems();
298
- if (!items.length) return 0;
299
-
300
- let inserted = 0;
301
- for (let i = 0; i < items.length; i++) {
302
- const li = items[i];
303
- if (!li || !li.isConnected) continue;
304
-
305
- // Avoid re-processing anchors already evaluated
306
- if (state.seenTopicAnchors.has(li)) continue;
307
-
308
- const pos = i + 1;
309
- const ok = (first && pos === 1) || (pos % interval === 0);
310
- state.seenTopicAnchors.add(li);
311
- if (!ok) continue;
312
-
313
- if (hasAdImmediatelyAfter(li)) continue;
314
-
315
- const id = nextId('between');
316
- if (!id) break;
317
-
318
- state.usedTopics.add(id);
319
- const wrap = insertAfter(li, id, 'ezoic-ad-between', pos);
320
- if (!wrap) continue;
321
-
322
- inserted += 1;
323
-
324
- callShowAdsWhenReady(id);
325
-
326
- if (inserted >= MAX_INSERTS_PER_RUN) break;
274
+ function computeTargets(count, interval, showFirst) {
275
+ const out = [];
276
+ if (count <= 0) return out;
277
+ if (showFirst) out.push(1);
278
+ for (let i = 1; i <= count; i++) {
279
+ if (i % interval === 0) out.push(i);
327
280
  }
328
- return inserted;
281
+ return Array.from(new Set(out)).sort((a, b) => a - b);
329
282
  }
330
283
 
331
- function injectPosts(cfg) {
332
- if (!normalizeBool(cfg.enableMessageAds)) return 0;
333
-
334
- const interval = Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3);
335
- const first = normalizeBool(cfg.showFirstMessageAd);
336
-
337
- const posts = getPostContainers();
338
- if (!posts.length) return 0;
284
+ function injectBetween(kindClass, items, interval, showFirst, kindPool, usedSet) {
285
+ if (!items.length) return 0;
286
+ const targets = computeTargets(items.length, interval, showFirst);
339
287
 
340
288
  let inserted = 0;
341
- for (let i = 0; i < posts.length; i++) {
342
- const post = posts[i];
343
- if (!post || !post.isConnected) continue;
289
+ for (const afterPos of targets) {
290
+ if (inserted >= MAX_INSERTS_PER_RUN) break;
344
291
 
345
- if (state.seenPostAnchors.has(post)) continue;
292
+ const el = items[afterPos - 1];
293
+ if (!el || !el.isConnected) continue;
346
294
 
347
- const no = i + 1;
348
- const ok = (first && no === 1) || (no % interval === 0);
349
- state.seenPostAnchors.add(post);
350
- if (!ok) continue;
295
+ // Prevent back-to-back at load
296
+ const prevWrap = findWrap(kindClass, afterPos - 1);
297
+ if (prevWrap) continue;
351
298
 
352
- if (hasAdImmediatelyAfter(post)) continue;
299
+ if (findWrap(kindClass, afterPos)) continue;
353
300
 
354
- const id = nextId('message');
301
+ const id = nextId(kindPool);
355
302
  if (!id) break;
356
303
 
357
- state.usedPosts.add(id);
358
- const wrap = insertAfter(post, id, 'ezoic-ad-message', no);
304
+ usedSet.add(id);
305
+ const wrap = insertAfter(el, id, kindClass, afterPos);
359
306
  if (!wrap) continue;
360
307
 
361
- inserted += 1;
362
-
363
308
  callShowAdsWhenReady(id);
364
-
365
- if (inserted >= MAX_INSERTS_PER_RUN) break;
309
+ inserted += 1;
366
310
  }
367
311
  return inserted;
368
312
  }
369
313
 
370
-
371
- function destroyUsedPlaceholders() {
372
- const ids = [];
373
- try {
374
- state.usedTopics.forEach((id) => ids.push(id));
375
- state.usedPosts.forEach((id) => ids.push(id));
376
- } catch (e) {}
377
-
378
- if (!ids.length) return;
379
-
380
- const call = () => {
381
- try {
382
- if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') {
383
- window.ezstandalone.destroyPlaceholders(ids);
384
- }
385
- } catch (e) {}
386
- };
387
-
388
- try {
389
- window.ezstandalone = window.ezstandalone || {};
390
- window.ezstandalone.cmd = window.ezstandalone.cmd || [];
391
-
392
- if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') {
393
- call();
394
- } else {
395
- // queue for when ezstandalone becomes ready
396
- window.ezstandalone.cmd.push(call);
397
- }
398
- } catch (e) {}
314
+ function enforceNoAdjacentAds() {
315
+ const ads = Array.from(document.querySelectorAll(`.${WRAP_CLASS}`));
316
+ for (let i = 0; i < ads.length; i++) {
317
+ const ad = ads[i];
318
+ const prev = ad.previousElementSibling;
319
+ if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) ad.style.display = 'none';
320
+ else ad.style.display = '';
321
+ }
399
322
  }
400
323
 
401
324
  function cleanup() {
402
- // Destroy slots for IDs we used on the previous view before we reuse the same IDs on the next page
403
325
  destroyUsedPlaceholders();
326
+
404
327
  state.pageKey = getPageKey();
405
328
  state.cfg = null;
406
329
  state.cfgPromise = null;
407
330
 
408
331
  state.poolTopics = [];
409
332
  state.poolPosts = [];
333
+ state.poolCategories = [];
410
334
  state.usedTopics.clear();
411
335
  state.usedPosts.clear();
412
-
413
- state.seenTopicAnchors = new WeakSet();
414
- state.seenPostAnchors = new WeakSet();
336
+ state.usedCategories.clear();
415
337
 
416
338
  state.lastShowById = new Map();
417
339
  state.pendingById = new Set();
418
340
 
341
+ state.attempts = 0;
342
+
419
343
  document.querySelectorAll(`.${WRAP_CLASS}`).forEach(el => el.remove());
420
344
 
421
345
  if (state.obs) { try { state.obs.disconnect(); } catch (e) {} state.obs = null; }
@@ -426,14 +350,13 @@
426
350
 
427
351
  function ensureObserver() {
428
352
  if (state.obs) return;
429
- state.obs = new MutationObserver(() => scheduleRun('dom-mutation'));
353
+ state.obs = new MutationObserver(() => scheduleRun('mutation'));
430
354
  try { state.obs.observe(document.body, { childList: true, subtree: true }); } catch (e) {}
431
- setTimeout(() => { if (state.obs) { try { state.obs.disconnect(); } catch (e) {} state.obs = null; } }, 12000);
355
+ setTimeout(() => { if (state.obs) { try { state.obs.disconnect(); } catch (e) {} state.obs = null; } }, 15000);
432
356
  }
433
357
 
434
358
  async function runCore() {
435
359
  patchShowAds();
436
- patchShowAds();
437
360
 
438
361
  const cfg = await fetchConfig();
439
362
  if (!cfg || cfg.excluded) return;
@@ -443,77 +366,106 @@
443
366
  const kind = getKind();
444
367
  let inserted = 0;
445
368
 
446
- if (kind === 'topic') inserted = injectPosts(cfg);
447
- else inserted = injectTopics(cfg);
369
+ if (kind === 'topic') {
370
+ if (normalizeBool(cfg.enableMessageAds)) {
371
+ inserted = injectBetween('ezoic-ad-message', getPostContainers(),
372
+ Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3),
373
+ normalizeBool(cfg.showFirstMessageAd),
374
+ 'message',
375
+ state.usedPosts);
376
+ }
377
+ } else if (kind === 'categoryTopics') {
378
+ if (normalizeBool(cfg.enableBetweenAds)) {
379
+ inserted = injectBetween('ezoic-ad-between', getTopicItems(),
380
+ Math.max(1, parseInt(cfg.intervalPosts, 10) || 6),
381
+ normalizeBool(cfg.showFirstTopicAd),
382
+ 'between',
383
+ state.usedTopics);
384
+ }
385
+ } else if (kind === 'categories') {
386
+ if (normalizeBool(cfg.enableCategoryAds)) {
387
+ inserted = injectBetween('ezoic-ad-categories', getCategoryItems(),
388
+ Math.max(1, parseInt(cfg.intervalCategories, 10) || 4),
389
+ normalizeBool(cfg.showFirstCategoryAd),
390
+ 'categories',
391
+ state.usedCategories);
392
+ }
393
+ }
448
394
 
449
395
  enforceNoAdjacentAds();
450
396
 
451
- // If we inserted max per run, schedule another pass to gradually fill (avoids “burst”)
452
- if (inserted >= MAX_INSERTS_PER_RUN) {
453
- setTimeout(() => scheduleRun('continue-fill'), 120);
397
+ // If nothing inserted and list isn't in DOM yet (first click), retry a bit
398
+ let count = 0;
399
+ if (kind === 'topic') count = getPostContainers().length;
400
+ else if (kind === 'categoryTopics') count = getTopicItems().length;
401
+ else if (kind === 'categories') count = getCategoryItems().length;
402
+
403
+ if (count === 0 && state.attempts < 25) {
404
+ state.attempts += 1;
405
+ setTimeout(() => scheduleRun('await-items'), 120);
406
+ return;
454
407
  }
408
+
409
+ if (inserted >= MAX_INSERTS_PER_RUN) setTimeout(() => scheduleRun('continue'), 140);
455
410
  }
456
411
 
457
- function scheduleRun(reason) {
412
+ function scheduleRun() {
458
413
  if (state.scheduled) return;
459
414
  state.scheduled = true;
460
415
 
461
416
  clearTimeout(state.timer);
462
417
  state.timer = setTimeout(() => {
463
418
  state.scheduled = false;
464
- // Ensure we're still on same page
465
419
  const pk = getPageKey();
466
420
  if (state.pageKey && pk !== state.pageKey) return;
467
421
  runCore().catch(() => {});
468
422
  }, 80);
423
+ }
469
424
 
470
425
  function bind() {
471
426
  if (!$) return;
472
427
 
473
428
  $(window).off('.ezoicInfinite');
474
429
 
475
- $(window).on('action:ajaxify.start.ezoicInfinite', () => {
476
- cleanup();
477
- });
430
+ $(window).on('action:ajaxify.start.ezoicInfinite', () => cleanup());
478
431
 
479
432
  $(window).on('action:ajaxify.end.ezoicInfinite', () => {
480
433
  state.pageKey = getPageKey();
481
434
  ensureObserver();
482
- scheduleRun('ajaxify.end');
483
- setTimeout(() => scheduleRun('ajaxify.end+250'), 250);
484
- setTimeout(() => scheduleRun('ajaxify.end+800'), 800);
435
+ scheduleRun();
436
+ setTimeout(scheduleRun, 200);
437
+ setTimeout(scheduleRun, 700);
485
438
  });
486
439
 
487
440
  $(window).on('action:category.loaded.ezoicInfinite', () => {
488
441
  ensureObserver();
489
- scheduleRun('category.loaded');
490
- setTimeout(() => scheduleRun('category.loaded+250'), 250);
442
+ scheduleRun();
443
+ setTimeout(scheduleRun, 250);
491
444
  });
492
445
 
493
446
  $(window).on('action:topics.loaded.ezoicInfinite', () => {
494
447
  ensureObserver();
495
- scheduleRun('topics.loaded');
496
- setTimeout(() => scheduleRun('topics.loaded+150'), 150);
448
+ scheduleRun();
449
+ setTimeout(scheduleRun, 150);
497
450
  });
498
451
 
499
452
  $(window).on('action:topic.loaded.ezoicInfinite', () => {
500
453
  ensureObserver();
501
- scheduleRun('topic.loaded');
502
- setTimeout(() => scheduleRun('topic.loaded+200'), 200);
454
+ scheduleRun();
455
+ setTimeout(scheduleRun, 200);
503
456
  });
504
457
 
505
458
  $(window).on('action:posts.loaded.ezoicInfinite', () => {
506
459
  ensureObserver();
507
- scheduleRun('posts.loaded');
508
- setTimeout(() => scheduleRun('posts.loaded+150'), 150);
460
+ scheduleRun();
461
+ setTimeout(scheduleRun, 150);
509
462
  });
510
463
  }
511
464
 
512
- // Boot
513
465
  cleanup();
514
466
  bind();
515
467
  ensureObserver();
516
468
  state.pageKey = getPageKey();
517
- scheduleRun('boot');
518
- setTimeout(() => scheduleRun('boot+250'), 250);
519
- })();
469
+ scheduleRun();
470
+ setTimeout(scheduleRun, 250);
471
+ })();
@@ -26,6 +26,32 @@
26
26
 
27
27
  <hr/>
28
28
 
29
+
30
+ <hr/>
31
+
32
+ <h4 class="mt-3">Pubs entre les catégories (page d’accueil)</h4>
33
+ <p class="form-text">Insère des pubs entre les catégories sur la page d’accueil (liste des catégories).</p>
34
+
35
+ <div class="form-check mb-3">
36
+ <input class="form-check-input" type="checkbox" id="enableCategoryAds" name="enableCategoryAds">
37
+ <label class="form-check-label" for="enableCategoryAds">Activer les pubs entre les catégories</label>
38
+ <div class="form-check mt-2">
39
+ <input class="form-check-input" type="checkbox" name="showFirstCategoryAd" />
40
+ <label class="form-check-label">Afficher une pub après la 1ère catégorie</label>
41
+ </div>
42
+ </div>
43
+
44
+ <div class="mb-3">
45
+ <label class="form-label" for="categoryPlaceholderIds">Pool d’IDs Ezoic (catégories)</label>
46
+ <textarea id="categoryPlaceholderIds" name="categoryPlaceholderIds" class="form-control" rows="4">{categoryPlaceholderIds}</textarea>
47
+ <p class="form-text">IDs numériques, un par ligne. Utilise un pool dédié (différent des pools topics/messages).</p>
48
+ </div>
49
+
50
+ <div class="mb-3">
51
+ <label class="form-label" for="intervalCategories">Afficher une pub toutes les N catégories</label>
52
+ <input type="number" id="intervalCategories" name="intervalCategories" class="form-control" value="{intervalCategories}" min="1">
53
+ </div>
54
+
29
55
  <h4 class="mt-3">Pubs “message” entre les réponses</h4>
30
56
  <p class="form-text">Insère un bloc qui ressemble à un post, toutes les N réponses (dans une page topic).</p>
31
57