nodebb-plugin-ezoic-infinite 1.6.26 → 1.6.28

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.6.26",
3
+ "version": "1.6.28",
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,43 +1,6 @@
1
1
  (function () {
2
2
  'use strict';
3
3
 
4
- function ezoicInsertAfterTopicHost(target, wrap, kindClass) {
5
- try {
6
- if (!target || !wrap) return false;
7
- if (kindClass !== 'ezoic-ad-between') return false;
8
-
9
- // Ensure we insert a LI host that looks like a topic item so NodeBB list virtualization keeps ordering.
10
- var anchorLi = target.closest ? (target.closest('li[component="category/topic"]') || target.closest('li')) : null;
11
- if (!anchorLi && target.tagName === 'LI') anchorLi = target;
12
- if (!anchorLi) return false;
13
-
14
- var ul = anchorLi.parentElement;
15
- if (!ul || !(ul.tagName === 'UL' || ul.tagName === 'OL')) return false;
16
-
17
- // If wrap already hosted, do nothing
18
- var existingHost = wrap.closest ? wrap.closest('li.nodebb-ezoic-host') : null;
19
- if (existingHost) return true;
20
-
21
- var host = document.createElement('li');
22
- host.className = 'nodebb-ezoic-host';
23
- // mimic topic list item so NodeBB doesn't relocate it
24
- host.setAttribute('component', 'category/topic');
25
- host.setAttribute('data-ezoic-host', 'between');
26
- host.style.listStyle = 'none';
27
- host.style.width = '100%';
28
-
29
- // Insert host after anchorLi
30
- if (anchorLi.insertAdjacentElement) anchorLi.insertAdjacentElement('afterend', host);
31
- else ul.insertBefore(host, anchorLi.nextSibling);
32
-
33
- host.appendChild(wrap);
34
- try { wrap.style.width = '100%'; } catch (e) {}
35
- return true;
36
- } catch (e) {}
37
- return false;
38
- }
39
-
40
-
41
4
  // Track scroll direction to avoid aggressive recycling when the user scrolls upward.
42
5
  // Recycling while scrolling up is a common cause of ads "bunching" and a "disappearing too fast" feeling.
43
6
  let lastScrollY = 0;
@@ -705,8 +668,7 @@ function globalGapFixInit() {
705
668
  insertingIds.add(id);
706
669
  try {
707
670
  const wrap = buildWrap(id, kindClass, afterPos, !existingPh);
708
- if (ezoicInsertAfterTopicHost(target, wrap, kindClass)) return;
709
- target.insertAdjacentElement('afterend', wrap);
671
+ target.insertAdjacentElement('afterend', wrap);
710
672
 
711
673
  // If placeholder exists elsewhere (including pool), move it into the wrapper.
712
674
  if (existingPh) {
@@ -1198,8 +1160,7 @@ function buildOrdinalMap(items) {
1198
1160
  if (!anchorEl || !wrap || !wrap.isConnected) return null;
1199
1161
 
1200
1162
  wrap.setAttribute('data-ezoic-after', String(afterPos));
1201
- if (ezoicInsertAfterTopicHost(anchorEl, wrap, kindClass)) return;
1202
- anchorEl.insertAdjacentElement('afterend', wrap);
1163
+ anchorEl.insertAdjacentElement('afterend', wrap);
1203
1164
 
1204
1165
  // Ensure minimal layout impact.
1205
1166
  try { wrap.style.contain = 'layout style paint'; } catch (e) {}
@@ -1550,27 +1511,184 @@ function buildOrdinalMap(items) {
1550
1511
 
1551
1512
 
1552
1513
 
1553
- // ===== V16 repair invalid UL children (between) =====
1554
- function ezoicRepairBetweenHosts() {
1555
- try {
1556
- var bad = document.querySelectorAll('ul > div.nodebb-ezoic-wrap.ezoic-ad-between, ol > div.nodebb-ezoic-wrap.ezoic-ad-between');
1557
- bad.forEach(function(wrap){
1514
+ // ===== V17 minimal pile-fix (no insert hooks) =====
1515
+ (function () {
1516
+ // Goal: keep ad injection intact. Only repair when we detect "pile-up" of between wraps.
1517
+ var TOPIC_LI_SEL = 'li[component="category/topic"]';
1518
+ var BETWEEN_WRAP_SEL = 'div.nodebb-ezoic-wrap.ezoic-ad-between';
1519
+ var HOST_CLASS = 'nodebb-ezoic-host';
1520
+
1521
+ var scheduled = false;
1522
+ var lastRun = 0;
1523
+ var COOLDOWN = 180;
1524
+
1525
+ function getTopicList() {
1526
+ try {
1527
+ var li = document.querySelector(TOPIC_LI_SEL);
1528
+ if (!li) return null;
1529
+ return li.closest ? li.closest('ul,ol') : null;
1530
+ } catch (e) { return null; }
1531
+ }
1532
+
1533
+ function isHost(node) {
1534
+ return !!(node && node.nodeType === 1 && node.tagName === 'LI' && node.classList && node.classList.contains(HOST_CLASS));
1535
+ }
1536
+
1537
+ function ensureHostForWrap(wrap, ul) {
1538
+ try {
1539
+ if (!wrap || wrap.nodeType !== 1) return null;
1540
+ if (!(wrap.matches && wrap.matches(BETWEEN_WRAP_SEL))) return null;
1541
+
1542
+ var host = wrap.closest ? wrap.closest('li.' + HOST_CLASS) : null;
1543
+ if (host) return host;
1544
+
1545
+ if (!ul) ul = wrap.closest ? wrap.closest('ul,ol') : null;
1546
+ if (!ul || !(ul.tagName === 'UL' || ul.tagName === 'OL')) return null;
1547
+
1548
+ // Only wrap if direct child of list (invalid / fragile)
1549
+ if (wrap.parentElement === ul) {
1550
+ host = document.createElement('li');
1551
+ host.className = HOST_CLASS;
1552
+ host.setAttribute('role', 'listitem');
1553
+ host.style.listStyle = 'none';
1554
+ host.style.width = '100%';
1555
+ ul.insertBefore(host, wrap);
1556
+ host.appendChild(wrap);
1557
+ try { wrap.style.width = '100%'; } catch (e) {}
1558
+ return host;
1559
+ }
1560
+ } catch (e) {}
1561
+ return null;
1562
+ }
1563
+
1564
+ function previousTopicLi(node) {
1565
+ try {
1566
+ var prev = node.previousElementSibling;
1567
+ while (prev) {
1568
+ if (prev.matches && prev.matches(TOPIC_LI_SEL)) return prev;
1569
+ // skip other hosts/wraps
1570
+ prev = prev.previousElementSibling;
1571
+ }
1572
+ } catch (e) {}
1573
+ return null;
1574
+ }
1575
+
1576
+ function detectPileUp(ul) {
1577
+ // Pile-up signature: 2+ between wraps/hosts adjacent with no topics between, often near top.
1578
+ try {
1579
+ var kids = ul.children;
1580
+ var run = 0;
1581
+ var maxRun = 0;
1582
+ for (var i = 0; i < kids.length; i++) {
1583
+ var el = kids[i];
1584
+ var isBetween = false;
1585
+ if (isHost(el)) {
1586
+ isBetween = !!(el.querySelector && el.querySelector(BETWEEN_WRAP_SEL));
1587
+ } else if (el.matches && el.matches(BETWEEN_WRAP_SEL)) {
1588
+ isBetween = true;
1589
+ }
1590
+ if (isBetween) {
1591
+ run++;
1592
+ if (run > maxRun) maxRun = run;
1593
+ } else if (el.matches && el.matches(TOPIC_LI_SEL)) {
1594
+ run = 0;
1595
+ } else {
1596
+ // other nodes reset lightly
1597
+ run = 0;
1598
+ }
1599
+ }
1600
+ return maxRun >= 2;
1601
+ } catch (e) {}
1602
+ return false;
1603
+ }
1604
+
1605
+ function redistribute(ul) {
1606
+ try {
1607
+ if (!ul) return;
1608
+
1609
+ // Step 1: wrap any direct child between DIVs into LI hosts (makes list stable)
1610
+ ul.querySelectorAll(':scope > ' + BETWEEN_WRAP_SEL).forEach(function(w){ ensureHostForWrap(w, ul); });
1611
+
1612
+ // Step 2: only act if we see pile-up (avoid touching infinite scroll during normal flow)
1613
+ if (!detectPileUp(ul)) return;
1614
+
1615
+ // Move each host to immediately after the closest previous topic LI at its current position.
1616
+ var hosts = ul.querySelectorAll(':scope > li.' + HOST_CLASS);
1617
+ hosts.forEach(function(host){
1618
+ try {
1619
+ var wrap = host.querySelector && host.querySelector(BETWEEN_WRAP_SEL);
1620
+ if (!wrap) return;
1621
+
1622
+ var anchor = previousTopicLi(host);
1623
+ if (!anchor) return; // if none, don't move (prevents yanking to top/bottom)
1624
+
1625
+ if (host.previousElementSibling !== anchor) {
1626
+ anchor.insertAdjacentElement('afterend', host);
1627
+ }
1628
+ } catch (e) {}
1629
+ });
1630
+ } catch (e) {}
1631
+ }
1632
+
1633
+ function schedule(reason) {
1634
+ var now = Date.now();
1635
+ if (now - lastRun < COOLDOWN) return;
1636
+ if (scheduled) return;
1637
+ scheduled = true;
1638
+ requestAnimationFrame(function () {
1639
+ scheduled = false;
1640
+ lastRun = Date.now();
1558
1641
  try {
1559
- var prev = wrap.previousElementSibling;
1560
- if (!prev) return;
1561
- ezoicInsertAfterTopicHost(prev, wrap, 'ezoic-ad-between');
1642
+ var ul = getTopicList();
1643
+ if (!ul) return;
1644
+ redistribute(ul);
1562
1645
  } catch (e) {}
1563
1646
  });
1564
- } catch (e) {}
1565
- }
1566
- try { ezoicRepairBetweenHosts(); } catch (e) {}
1567
- if (window.jQuery) {
1568
- try {
1569
- window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1570
- setTimeout(function(){ try { ezoicRepairBetweenHosts(); } catch(e) {} }, 0);
1571
- setTimeout(function(){ try { ezoicRepairBetweenHosts(); } catch(e) {} }, 250);
1572
- });
1573
- } catch (e) {}
1574
- }
1575
- // ===== /V16 =====
1647
+ }
1648
+
1649
+ function init() {
1650
+ schedule('init');
1651
+
1652
+ // Observe only the topic list once available
1653
+ try {
1654
+ if (typeof MutationObserver !== 'undefined') {
1655
+ var observeList = function(ul){
1656
+ if (!ul) return;
1657
+ var mo = new MutationObserver(function(muts){
1658
+ // schedule on any change; redistribute() itself is guarded by pile-up detection
1659
+ schedule('mo');
1660
+ });
1661
+ mo.observe(ul, { childList: true, subtree: true });
1662
+ };
1663
+
1664
+ var ul = getTopicList();
1665
+ if (ul) observeList(ul);
1666
+ else {
1667
+ var mo2 = new MutationObserver(function(){
1668
+ var u2 = getTopicList();
1669
+ if (u2) {
1670
+ try { observeList(u2); } catch(e){}
1671
+ try { mo2.disconnect(); } catch(e){}
1672
+ }
1673
+ });
1674
+ mo2.observe(document.documentElement || document.body, { childList: true, subtree: true });
1675
+ }
1676
+ }
1677
+ } catch (e) {}
1678
+
1679
+ // NodeBB events: run after infinite scroll batches
1680
+ if (window.jQuery) {
1681
+ try {
1682
+ window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
1683
+ setTimeout(function(){ schedule('event'); }, 50);
1684
+ setTimeout(function(){ schedule('event2'); }, 400);
1685
+ });
1686
+ } catch (e) {}
1687
+ }
1688
+ }
1689
+
1690
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
1691
+ else init();
1692
+ })();
1693
+ // ===== /V17 =====
1576
1694
 
package/public/style.css CHANGED
@@ -81,14 +81,8 @@
81
81
  }
82
82
 
83
83
 
84
- /* ===== V16 host topic li ===== */
85
- li.nodebb-ezoic-host[component="category/topic"]{
86
- list-style:none;
87
- width:100%;
88
- }
89
- /* ensure host doesn't inherit topic card layout */
90
- li.nodebb-ezoic-host[component="category/topic"] > .nodebb-ezoic-wrap.ezoic-ad-between{
91
- width:100%;
92
- }
93
- /* ===== /V16 ===== */
84
+ /* ===== V17 host styling ===== */
85
+ li.nodebb-ezoic-host { list-style: none; width: 100%; display: block; }
86
+ li.nodebb-ezoic-host > .nodebb-ezoic-wrap.ezoic-ad-between { width: 100%; display: block; }
87
+ /* ===== /V17 ===== */
94
88