nodebb-plugin-ezoic-infinite 1.5.54 → 1.5.56

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.5.54",
3
+ "version": "1.5.56",
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
@@ -7,6 +7,89 @@
7
7
  const WRAP_CLASS = 'ezoic-ad';
8
8
  const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-';
9
9
 
10
+
11
+ // Offscreen pool to keep placeholder elements alive across ajaxify/navigation.
12
+ // This prevents Ezoic from trying to define ids that are not currently injected,
13
+ // and eliminates "HTML element with id ... does not exist" noise.
14
+ const POOL_ID = 'ezoic-placeholder-pool';
15
+
16
+ function ensurePool() {
17
+ let pool = document.getElementById(POOL_ID);
18
+ if (pool) return pool;
19
+ pool = document.createElement('div');
20
+ pool.id = POOL_ID;
21
+ pool.style.position = 'absolute';
22
+ pool.style.left = '-99999px';
23
+ pool.style.top = '0';
24
+ pool.style.width = '1px';
25
+ pool.style.height = '1px';
26
+ pool.style.overflow = 'hidden';
27
+ pool.setAttribute('aria-hidden', 'true');
28
+ // Attach early (documentElement exists before body), so placeholders are always connected.
29
+ try { (document.documentElement || document.body).appendChild(pool); } catch (e) {}
30
+ // If body exists later, move it under body (purely cosmetic).
31
+ try {
32
+ if (document.body && pool.parentNode !== document.body) {
33
+ document.body.appendChild(pool);
34
+ }
35
+ } catch (e) {}
36
+ return pool;
37
+ }
38
+
39
+ // Create placeholder divs for all configured ids upfront.
40
+ // Ezoic sometimes attempts to initialize/refresh a range of ids even if we
41
+ // haven't injected them yet; keeping them in the offscreen pool prevents
42
+ // "HTML element ... does not exist" spam.
43
+ function primePool(ids) {
44
+ try {
45
+ if (!ids || !ids.length) return;
46
+ const pool = ensurePool();
47
+ for (const v of ids) {
48
+ const id = parseInt(v, 10);
49
+ if (!Number.isFinite(id) || id <= 0) continue;
50
+ const domId = `${PLACEHOLDER_PREFIX}${id}`;
51
+ let ph = document.getElementById(domId);
52
+ if (!ph) {
53
+ ph = document.createElement('div');
54
+ ph.id = domId;
55
+ ph.setAttribute('data-ezoic-id', String(id));
56
+ pool.appendChild(ph);
57
+ } else if (!ph.isConnected) {
58
+ pool.appendChild(ph);
59
+ }
60
+ }
61
+ } catch (e) {}
62
+ }
63
+
64
+ function acquirePlaceholder(id) {
65
+ const domId = `${PLACEHOLDER_PREFIX}${id}`;
66
+ let ph = document.getElementById(domId);
67
+ if (!ph) {
68
+ ph = document.createElement('div');
69
+ ph.id = domId;
70
+ ph.setAttribute('data-ezoic-id', String(id));
71
+ ensurePool().appendChild(ph);
72
+ }
73
+ // Detach from wherever it currently is (pool or a previous wrap)
74
+ try { if (ph.parentNode) ph.parentNode.removeChild(ph); } catch (e) {}
75
+ // Clear request/defined flags when reusing
76
+ try {
77
+ if (ph.dataset) {
78
+ ph.dataset.ezRequested = '0';
79
+ ph.dataset.ezDefined = '0';
80
+ }
81
+ } catch (e) {}
82
+ return ph;
83
+ }
84
+
85
+ function parkPlaceholderFromWrap(wrap) {
86
+ try {
87
+ const ph = wrap && wrap.querySelector ? wrap.querySelector(`[id^="${PLACEHOLDER_PREFIX}"]`) : null;
88
+ if (!ph) return;
89
+ try { if (state && state.io) state.io.unobserve(ph); } catch (e) {}
90
+ ensurePool().appendChild(ph);
91
+ } catch (e) {}
92
+ }
10
93
  // Insert at most N ads per run to keep the UI smooth on infinite scroll
11
94
  const MAX_INSERTS_PER_RUN = 3;
12
95
 
@@ -358,6 +441,12 @@ function withInternalDomChange(fn) {
358
441
  if (state.allTopics.length === 0) state.allTopics = parsePool(cfg.placeholderIds);
359
442
  if (state.allPosts.length === 0) state.allPosts = parsePool(cfg.messagePlaceholderIds);
360
443
  if (state.allCategories.length === 0) state.allCategories = parsePool(cfg.categoryPlaceholderIds);
444
+
445
+ // Keep placeholders alive even before they're injected.
446
+ // This avoids Ezoic trying to touch ids that we haven't inserted yet.
447
+ primePool(state.allTopics);
448
+ primePool(state.allPosts);
449
+ primePool(state.allCategories);
361
450
  }
362
451
 
363
452
  // ---------- insertion primitives ----------
@@ -422,6 +511,7 @@ function withInternalDomChange(fn) {
422
511
  withInternalDomChange(() => {
423
512
  try {
424
513
  if (id) safeDestroyById(id);
514
+ parkPlaceholderFromWrap(wrap);
425
515
  wrap.remove();
426
516
  } catch (e) {}
427
517
  });
@@ -455,9 +545,7 @@ function buildWrap(id, kindClass, afterPos) {
455
545
  wrap.setAttribute('data-ezoic-wrapid', String(id));
456
546
  wrap.style.width = '100%';
457
547
 
458
- const ph = document.createElement('div');
459
- ph.id = `${PLACEHOLDER_PREFIX}${id}`;
460
- ph.setAttribute('data-ezoic-id', String(id));
548
+ const ph = acquirePlaceholder(id);
461
549
  wrap.appendChild(ph);
462
550
 
463
551
  return wrap;
@@ -524,6 +612,7 @@ function buildWrap(id, kindClass, afterPos) {
524
612
  if (ph && state.io) state.io.unobserve(ph);
525
613
  } catch (e) {}
526
614
 
615
+ parkPlaceholderFromWrap(victim);
527
616
  victim.remove();
528
617
  return true;
529
618
  } catch (e) {
@@ -735,6 +824,25 @@ function startShow(id) {
735
824
  inserted += 1;
736
825
  }
737
826
 
827
+ // Safety: if DOM churn results in two consecutive ad wrappers, remove the latter.
828
+ // (This can happen when NodeBB inserts/removes spacer elements around the anchor.)
829
+ try {
830
+ const wraps = Array.from(document.querySelectorAll(`.${WRAP_CLASS}.${kindClass}`));
831
+ for (const w of wraps) {
832
+ const prev = w.previousElementSibling;
833
+ if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) {
834
+ withInternalDomChange(() => {
835
+ try {
836
+ const id = getWrapIdFromWrap(w);
837
+ if (id) safeDestroyById(id);
838
+ parkPlaceholderFromWrap(w);
839
+ w.remove();
840
+ } catch (e) {}
841
+ });
842
+ }
843
+ }
844
+ } catch (e) {}
845
+
738
846
  return inserted;
739
847
  }
740
848
 
@@ -870,6 +978,7 @@ function startShow(id) {
870
978
  // remove all wrappers
871
979
  try {
872
980
  document.querySelectorAll(`.${WRAP_CLASS}`).forEach((el) => {
981
+ try { parkPlaceholderFromWrap(el); } catch (e) {}
873
982
  try { el.remove(); } catch (e) {}
874
983
  });
875
984
  } catch (e) {}
package/public/style.css CHANGED
@@ -38,3 +38,5 @@
38
38
  .ezoic-ad > [id^="ezoic-pub-ad-placeholder-"] {
39
39
  min-height: 0 !important;
40
40
  }
41
+
42
+ #ezoic-placeholder-pool{display:block;}