nodebb-plugin-ezoic-infinite 1.6.2 → 1.6.4

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 +94 -44
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
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
@@ -1346,34 +1346,35 @@ function buildOrdinalMap(items) {
1346
1346
 
1347
1347
 
1348
1348
 
1349
+
1349
1350
  // ===== CLEAN REFRACTOR: visibility manager for Ezoic wraps =====
1350
1351
  (function () {
1351
- // v3 improvements:
1352
- // - Faster global rendering: larger preload margin, higher show throughput.
1353
- // - Reliable on UP-scroll: scroll-proximity scanner enqueues showAds for near-viewport wraps even if IO doesn't fire.
1352
+ // v2.2:
1353
+ // - Fix "after long scroll: no ads down / no ads up" caused by O(n) scans with a small budget.
1354
+ // - Replace the near-viewport scan with a viewport-walk using elementFromPoint + sibling traversal.
1355
+ // This stays O(k) even with thousands of posts.
1354
1356
 
1355
1357
  var BETWEEN_SELECTOR = '.nodebb-ezoic-wrap.ezoic-ad-between';
1356
1358
  var MESSAGE_SELECTOR = '.nodebb-ezoic-wrap.ezoic-ad-message';
1357
1359
  var WRAP_SELECTOR = BETWEEN_SELECTOR + ', ' + MESSAGE_SELECTOR;
1358
1360
 
1359
- // We never remove message wraps; only very conservative cleanup for between wraps.
1360
- var KEEP_MARGIN_BETWEEN_DESKTOP = 3200;
1361
- var KEEP_MARGIN_BETWEEN_MOBILE = 2300;
1361
+ var KEEP_MARGIN_BETWEEN_DESKTOP = 2600;
1362
+ var KEEP_MARGIN_BETWEEN_MOBILE = 1900;
1362
1363
 
1363
- // Show tuning
1364
- var SHOW_COOLDOWN_MS = 650; // faster re-show when coming back up
1365
- var MAX_SHOW_PER_TICK = 10; // higher throughput for long scroll pages
1364
+ // Show tuning (safe/moderate)
1365
+ var SHOW_COOLDOWN_MS = 900;
1366
+ var MAX_SHOW_PER_TICK = 6;
1366
1367
 
1367
- // Proximity scan (fix up-scroll non-display)
1368
- var PROXIMITY_SCAN_COOLDOWN_MS = 180;
1369
- var PROXIMITY_MARGIN_DESKTOP = 1400; // px above+below viewport
1370
- var PROXIMITY_MARGIN_MOBILE = 1100;
1368
+ // Viewport-walk tuning
1369
+ var WALK_COOLDOWN_MS = 180;
1370
+ var lastWalk = 0;
1371
+ var WALK_STEPS = 28; // siblings in each direction
1372
+ var WALK_POINTS = 2; // sample top & bottom area of viewport
1371
1373
 
1372
1374
  function isMobile() {
1373
1375
  try { return window.matchMedia && window.matchMedia('(max-width: 767px)').matches; } catch (e) { return false; }
1374
1376
  }
1375
1377
  function keepMarginBetween() { return isMobile() ? KEEP_MARGIN_BETWEEN_MOBILE : KEEP_MARGIN_BETWEEN_DESKTOP; }
1376
- function proximityMargin() { return isMobile() ? PROXIMITY_MARGIN_MOBILE : PROXIMITY_MARGIN_DESKTOP; }
1377
1378
 
1378
1379
  var lastShowById = Object.create(null);
1379
1380
  var showQueue = [];
@@ -1430,7 +1431,7 @@ function buildOrdinalMap(items) {
1430
1431
  if (removed >= 3) return;
1431
1432
  try {
1432
1433
  var lv = parseInt(w.getAttribute('data-last-visible') || '0', 10);
1433
- if (lv && (Date.now() - lv) < 15000) return;
1434
+ if (lv && (Date.now() - lv) < 12000) return;
1434
1435
 
1435
1436
  var r = w.getBoundingClientRect();
1436
1437
  if (r.bottom < -margin || r.top > ((window.innerHeight || 0) + margin)) {
@@ -1441,7 +1442,7 @@ function buildOrdinalMap(items) {
1441
1442
  });
1442
1443
  }
1443
1444
 
1444
- // IntersectionObserver: preload well before viewport
1445
+ // IO: preload earlier for faster display
1445
1446
  var io = null;
1446
1447
  function installIO() {
1447
1448
  if (io || typeof IntersectionObserver === 'undefined') return;
@@ -1457,12 +1458,11 @@ function buildOrdinalMap(items) {
1457
1458
  }
1458
1459
  });
1459
1460
  } catch (e) {}
1460
- }, { root: null, rootMargin: '1400px 0px 1400px 0px', threshold: 0.01 });
1461
+ }, { root: null, rootMargin: '1200px 0px 1200px 0px', threshold: 0.01 });
1461
1462
 
1462
1463
  try { document.querySelectorAll(WRAP_SELECTOR).forEach(function (w) { try { io.observe(w); } catch(e) {} }); } catch (e) {}
1463
1464
  }
1464
1465
 
1465
- // Observe new wraps
1466
1466
  var moInstalled = false;
1467
1467
  function installMO() {
1468
1468
  if (moInstalled || typeof MutationObserver === 'undefined') return;
@@ -1496,37 +1496,89 @@ function buildOrdinalMap(items) {
1496
1496
  try { mo.observe(document.documentElement || document.body, { childList: true, subtree: true }); } catch (e) {}
1497
1497
  }
1498
1498
 
1499
- // Proximity scanner: ensures re-show on up-scroll even if IO doesn't trigger (e.g. cached intersection state)
1500
- var lastScan = 0;
1501
- function proximityScan() {
1499
+ function closestWrap(el) {
1500
+ try {
1501
+ while (el && el !== document.body && el !== document.documentElement) {
1502
+ if (el.classList && el.classList.contains('nodebb-ezoic-wrap')) return el;
1503
+ el = el.parentElement;
1504
+ }
1505
+ } catch (e) {}
1506
+ return null;
1507
+ }
1508
+
1509
+ function enqueueWrap(w) {
1510
+ if (!w) return;
1511
+ var id = getWrapId(w) || getPlaceholderId(w);
1512
+ if (id) enqueueShow(id);
1513
+ }
1514
+
1515
+ // Viewport-walk: find content near viewport and walk siblings to pick nearby wraps.
1516
+ function viewportWalkEnqueue() {
1502
1517
  var now = Date.now();
1503
- if (now - lastScan < PROXIMITY_SCAN_COOLDOWN_MS) return;
1504
- lastScan = now;
1518
+ if (now - lastWalk < WALK_COOLDOWN_MS) return;
1519
+ lastWalk = now;
1520
+
1521
+ if (typeof document.elementFromPoint !== 'function') return;
1505
1522
 
1506
- var margin = proximityMargin();
1507
1523
  var vh = window.innerHeight || document.documentElement.clientHeight || 0;
1524
+ if (!vh) return;
1508
1525
 
1509
- var wraps;
1510
- try { wraps = document.querySelectorAll(WRAP_SELECTOR); } catch (e) { return; }
1526
+ // sample points: near top and near bottom
1527
+ var ys = [Math.min(vh - 10, 140), Math.max(10, vh - 180)];
1528
+ if (WALK_POINTS === 1) ys = [Math.min(vh - 10, 180)];
1511
1529
 
1512
- var budget = 12; // don't scan too heavy
1513
- for (var i = 0; i < wraps.length && budget > 0; i++) {
1514
- var w = wraps[i];
1515
- try {
1516
- var r = w.getBoundingClientRect();
1517
- if (r.bottom >= -margin && r.top <= (vh + margin)) {
1518
- var id = getWrapId(w) || getPlaceholderId(w);
1519
- if (id) enqueueShow(id);
1520
- budget--;
1521
- }
1522
- } catch (e) {}
1530
+ for (var p = 0; p < ys.length; p++) {
1531
+ var y = ys[p];
1532
+ var el = null;
1533
+ try { el = document.elementFromPoint(10, y); } catch (e) {}
1534
+ if (!el) continue;
1535
+
1536
+ // ascend to a stable list item, then traverse siblings
1537
+ var cursor = el;
1538
+ // try to find an item container to traverse in topic lists
1539
+ for (var i = 0; i < 8 && cursor && cursor.parentElement; i++) {
1540
+ if (cursor.classList && (cursor.classList.contains('topic-item') || cursor.classList.contains('posts-list') || cursor.classList.contains('category-item'))) break;
1541
+ cursor = cursor.parentElement;
1542
+ }
1543
+ // if cursor isn't traversable, just use the element itself
1544
+ cursor = cursor || el;
1545
+
1546
+ // find nearest wrap around this point
1547
+ enqueueWrap(closestWrap(el));
1548
+
1549
+ // walk next/prev siblings and enqueue wraps found
1550
+ var forward = cursor;
1551
+ for (var s = 0; s < WALK_STEPS; s++) {
1552
+ if (!forward) break;
1553
+ // check within forward node for wraps
1554
+ try {
1555
+ if (forward.matches && forward.matches(WRAP_SELECTOR)) enqueueWrap(forward);
1556
+ if (forward.querySelectorAll) {
1557
+ var w1 = forward.querySelectorAll(WRAP_SELECTOR);
1558
+ for (var wi = 0; wi < w1.length; wi++) enqueueWrap(w1[wi]);
1559
+ }
1560
+ } catch (e) {}
1561
+ forward = forward.nextElementSibling;
1562
+ }
1563
+
1564
+ var backward = cursor;
1565
+ for (var s2 = 0; s2 < WALK_STEPS; s2++) {
1566
+ if (!backward) break;
1567
+ try {
1568
+ if (backward.matches && backward.matches(WRAP_SELECTOR)) enqueueWrap(backward);
1569
+ if (backward.querySelectorAll) {
1570
+ var w2 = backward.querySelectorAll(WRAP_SELECTOR);
1571
+ for (var wj = 0; wj < w2.length; wj++) enqueueWrap(w2[wj]);
1572
+ }
1573
+ } catch (e) {}
1574
+ backward = backward.previousElementSibling;
1575
+ }
1523
1576
  }
1524
1577
  }
1525
1578
 
1526
- // Sweep only between cleanup
1527
1579
  var sweepPending = false;
1528
1580
  var lastSweep = 0;
1529
- var SWEEP_COOLDOWN_MS = 700;
1581
+ var SWEEP_COOLDOWN_MS = 600;
1530
1582
 
1531
1583
  function scheduleSweep() {
1532
1584
  var now = Date.now();
@@ -1542,7 +1594,7 @@ function buildOrdinalMap(items) {
1542
1594
 
1543
1595
  function onScroll() {
1544
1596
  scheduleSweep();
1545
- proximityScan();
1597
+ viewportWalkEnqueue();
1546
1598
  scheduleShowTick();
1547
1599
  }
1548
1600
 
@@ -1555,10 +1607,7 @@ function buildOrdinalMap(items) {
1555
1607
 
1556
1608
  if (window.jQuery) {
1557
1609
  window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function () {
1558
- setTimeout(function () {
1559
- installIO();
1560
- onScroll();
1561
- }, 0);
1610
+ setTimeout(function () { installIO(); onScroll(); }, 0);
1562
1611
  });
1563
1612
  }
1564
1613
 
@@ -1572,3 +1621,4 @@ function buildOrdinalMap(items) {
1572
1621
 
1573
1622
 
1574
1623
 
1624
+