nodebb-plugin-ezoic-infinite 1.5.63 → 1.5.64

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/public/client.js +87 -22
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.5.63",
3
+ "version": "1.5.64",
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
@@ -234,22 +234,40 @@
234
234
  window.__nodebbEzoicPatched = true;
235
235
  const orig = ez.showAds;
236
236
 
237
+ // Important: preserve the original calling convention.
238
+ // Some Ezoic builds expect an array; calling one-by-one can lead to
239
+ // repeated define attempts and "Placeholder Id ... already been defined".
237
240
  ez.showAds = function (...args) {
238
241
  if (isBlocked()) return;
239
242
 
243
+ const now = Date.now();
240
244
  let ids = [];
241
- if (args.length === 1 && Array.isArray(args[0])) ids = args[0];
245
+ const isArrayCall = (args.length === 1 && Array.isArray(args[0]));
246
+ if (isArrayCall) ids = args[0];
242
247
  else ids = args;
243
248
 
249
+ const filtered = [];
244
250
  const seen = new Set();
245
251
  for (const v of ids) {
246
252
  const id = parseInt(v, 10);
247
253
  if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
248
254
  const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
249
255
  if (!ph || !ph.isConnected) continue;
256
+
257
+ // Extra throttle to avoid rapid duplicate defines during ajaxify churn
258
+ const last = state.lastShowById.get(id) || 0;
259
+ if (now - last < 650) continue;
260
+ state.lastShowById.set(id, now);
261
+
250
262
  seen.add(id);
251
- try { orig.call(ez, id); } catch (e) {}
263
+ filtered.push(id);
252
264
  }
265
+
266
+ if (!filtered.length) return;
267
+ try {
268
+ if (isArrayCall) orig.call(ez, filtered);
269
+ else orig.apply(ez, filtered);
270
+ } catch (e) {}
253
271
  };
254
272
  } catch (e) {}
255
273
  };
@@ -347,11 +365,21 @@ function withInternalDomChange(fn) {
347
365
  // NodeBB can insert separators/spacers; accept an anchor within a few previous siblings
348
366
  let ok = false;
349
367
  let prev = wrap.previousElementSibling;
350
- for (let i = 0; i < 3 && prev; i++) {
368
+ for (let i = 0; i < 8 && prev; i++) {
351
369
  if (itemSet.has(prev)) { ok = true; break; }
352
370
  prev = prev.previousElementSibling;
353
371
  }
354
372
 
373
+ // If it is already filled (iframe/ins/img), be conservative and keep it.
374
+ // Prevents ads "disappearing too fast" during ajaxify churn / minor rerenders.
375
+ if (!ok) {
376
+ try {
377
+ const ph = wrap.querySelector && wrap.querySelector(`[id^="${PLACEHOLDER_PREFIX}"]`);
378
+ const filled = !!(ph && ph.querySelector && ph.querySelector('iframe, ins, img, .ez-ad, .ezoic-ad'));
379
+ if (filled) ok = true;
380
+ } catch (e) {}
381
+ }
382
+
355
383
  if (!ok) {
356
384
  const id = getWrapIdFromWrap(wrap);
357
385
  withInternalDomChange(() => {
@@ -368,6 +396,28 @@ function withInternalDomChange(fn) {
368
396
  return removed;
369
397
  }
370
398
 
399
+ function declusterWraps(kindClass) {
400
+ try {
401
+ const wraps = Array.from(document.querySelectorAll(`.${WRAP_CLASS}.${kindClass}`));
402
+ if (wraps.length < 2) return;
403
+ for (let i = 1; i < wraps.length; i++) {
404
+ const w = wraps[i];
405
+ if (!w || !w.isConnected) continue;
406
+ // If previous siblings contain another wrap within 2 hops, remove this one.
407
+ let prev = w.previousElementSibling;
408
+ let hops = 0;
409
+ while (prev && hops < 3) {
410
+ if (prev.classList && prev.classList.contains(WRAP_CLASS)) {
411
+ withInternalDomChange(() => { try { w.remove(); } catch (e) {} });
412
+ break;
413
+ }
414
+ prev = prev.previousElementSibling;
415
+ hops++;
416
+ }
417
+ }
418
+ } catch (e) {}
419
+ }
420
+
371
421
  function refreshEmptyState(id) {
372
422
  // After Ezoic has had a moment to fill the placeholder, toggle the CSS class.
373
423
  window.setTimeout(() => {
@@ -383,17 +433,25 @@ function withInternalDomChange(fn) {
383
433
  }, 3500);
384
434
  }
385
435
 
386
- function buildWrap(id, kindClass, afterPos) {
436
+ function buildWrap(id, kindClass, afterPos, existingPlaceholder) {
387
437
  const wrap = document.createElement('div');
388
438
  wrap.className = `${WRAP_CLASS} ${kindClass}`;
389
439
  wrap.setAttribute('data-ezoic-after', String(afterPos));
390
440
  wrap.setAttribute('data-ezoic-wrapid', String(id));
391
441
  wrap.style.width = '100%';
392
442
 
393
- const ph = document.createElement('div');
394
- ph.id = `${PLACEHOLDER_PREFIX}${id}`;
395
- ph.setAttribute('data-ezoic-id', String(id));
396
- wrap.appendChild(ph);
443
+ if (existingPlaceholder && existingPlaceholder.nodeType === 1) {
444
+ try {
445
+ existingPlaceholder.id = `${PLACEHOLDER_PREFIX}${id}`;
446
+ existingPlaceholder.setAttribute('data-ezoic-id', String(id));
447
+ } catch (e) {}
448
+ wrap.appendChild(existingPlaceholder);
449
+ } else {
450
+ const ph = document.createElement('div');
451
+ ph.id = `${PLACEHOLDER_PREFIX}${id}`;
452
+ ph.setAttribute('data-ezoic-id', String(id));
453
+ wrap.appendChild(ph);
454
+ }
397
455
 
398
456
  return wrap;
399
457
  }
@@ -407,24 +465,28 @@ function buildWrap(id, kindClass, afterPos) {
407
465
  if (findWrap(kindClass, afterPos)) return null;
408
466
  if (insertingIds.has(id)) return null;
409
467
 
410
- const existingPh = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
411
-
412
468
  insertingIds.add(id);
413
469
  try {
414
- const wrap = buildWrap(id, kindClass, afterPos);
415
- target.insertAdjacentElement('afterend', wrap);
416
-
417
- // If a placeholder with this id already exists elsewhere (some Ezoic flows
418
- // pre-create placeholders), move it into our wrapper instead of aborting.
419
- // replaceChild moves the node atomically (no detach window).
420
- if (existingPh && existingPh !== wrap.firstElementChild) {
470
+ const existingPh = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
471
+
472
+ // CRITICAL: never create a second element with the same id, even briefly.
473
+ // That can trigger "Placeholder Id ... already been defined" during load.
474
+ // If an existing placeholder already exists, move it into the new wrapper
475
+ // before inserting the wrapper into the DOM.
476
+ let moved = null;
477
+ if (existingPh && existingPh.isConnected) {
478
+ moved = existingPh;
479
+ // If it was inside one of our wrappers, drop that empty wrapper.
421
480
  try {
422
- existingPh.setAttribute('data-ezoic-id', String(id));
423
- wrap.replaceChild(existingPh, wrap.firstElementChild);
424
- } catch (e) {
425
- // Keep the new placeholder if replace fails.
426
- }
481
+ const oldWrap = moved.closest && moved.closest(`.${WRAP_CLASS}`);
482
+ if (oldWrap && oldWrap.parentNode) {
483
+ withInternalDomChange(() => { try { oldWrap.remove(); } catch (e) {} });
484
+ }
485
+ } catch (e) {}
427
486
  }
487
+
488
+ const wrap = buildWrap(id, kindClass, afterPos, moved);
489
+ target.insertAdjacentElement('afterend', wrap);
428
490
  return wrap;
429
491
  } finally {
430
492
  insertingIds.delete(id);
@@ -752,6 +814,7 @@ function startShow(id) {
752
814
  state.allPosts,
753
815
  'curPosts'
754
816
  );
817
+ declusterWraps('ezoic-ad-message');
755
818
  }
756
819
  } else if (kind === 'categoryTopics') {
757
820
  if (normalizeBool(cfg.enableBetweenAds)) {
@@ -765,6 +828,7 @@ function startShow(id) {
765
828
  state.allTopics,
766
829
  'curTopics'
767
830
  );
831
+ declusterWraps('ezoic-ad-between');
768
832
  }
769
833
  } else if (kind === 'categories') {
770
834
  if (normalizeBool(cfg.enableCategoryAds)) {
@@ -778,6 +842,7 @@ function startShow(id) {
778
842
  state.allCategories,
779
843
  'curCategories'
780
844
  );
845
+ declusterWraps('ezoic-ad-categories');
781
846
  }
782
847
  }
783
848
  }