nodebb-plugin-ezoic-infinite 1.6.21 → 1.6.23
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 +1 -1
- package/public/client.js +158 -127
- package/public/style.css +2 -2
- package/README.md +0 -15
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -1511,75 +1511,47 @@ function buildOrdinalMap(items) {
|
|
|
1511
1511
|
|
|
1512
1512
|
|
|
1513
1513
|
|
|
1514
|
-
// ===== V14.1.
|
|
1514
|
+
// ===== V14.1.3: Mutation-based reconcile (no scroll listener), place by data-ezoic-after =====
|
|
1515
1515
|
(function () {
|
|
1516
|
-
var
|
|
1517
|
-
var
|
|
1518
|
-
var
|
|
1516
|
+
var WRAP_SEL = 'div.nodebb-ezoic-wrap.ezoic-ad-between';
|
|
1517
|
+
var HOST_SEL = 'li.nodebb-ezoic-host';
|
|
1518
|
+
var TOPIC_LI_SEL = 'li[component="category/topic"]';
|
|
1519
1519
|
|
|
1520
|
-
var
|
|
1520
|
+
var freezeUntil = 0;
|
|
1521
1521
|
var pending = false;
|
|
1522
|
-
var
|
|
1523
|
-
var COOLDOWN = 140;
|
|
1522
|
+
var queue = new Set();
|
|
1524
1523
|
|
|
1525
|
-
function
|
|
1526
|
-
return window.pageYOffset || document.documentElement.scrollTop || 0;
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
function token() {
|
|
1530
|
-
try { return String(Date.now()) + '-' + Math.random().toString(16).slice(2); } catch (e) { return String(Date.now()); }
|
|
1531
|
-
}
|
|
1524
|
+
function now() { return Date.now(); }
|
|
1532
1525
|
|
|
1533
|
-
function
|
|
1526
|
+
function getTopicList() {
|
|
1534
1527
|
try {
|
|
1535
|
-
var
|
|
1536
|
-
if (
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
if (p.tagName === 'UL' || p.tagName === 'OL') return p;
|
|
1540
|
-
p = p.parentElement;
|
|
1541
|
-
}
|
|
1542
|
-
} catch (e) {}
|
|
1543
|
-
return null;
|
|
1528
|
+
var topic = document.querySelector(TOPIC_LI_SEL);
|
|
1529
|
+
if (!topic) return null;
|
|
1530
|
+
return topic.closest ? topic.closest('ul,ol') : null;
|
|
1531
|
+
} catch (e) { return null; }
|
|
1544
1532
|
}
|
|
1545
1533
|
|
|
1546
|
-
function
|
|
1534
|
+
function isLoading() {
|
|
1547
1535
|
try {
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
var host = cur.closest('li.' + HOST_CLASS);
|
|
1552
|
-
if (host) cur = host;
|
|
1553
|
-
}
|
|
1554
|
-
var prev = cur.previousElementSibling;
|
|
1555
|
-
while (prev) {
|
|
1556
|
-
if (prev.tagName === 'LI' && !(prev.classList && prev.classList.contains(HOST_CLASS))) return prev;
|
|
1557
|
-
prev = prev.previousElementSibling;
|
|
1558
|
-
}
|
|
1559
|
-
} catch (e) {}
|
|
1560
|
-
return null;
|
|
1536
|
+
if (now() < freezeUntil) return true;
|
|
1537
|
+
return !!document.querySelector('.infinite-loading, .infinite-scroll-loading, .loading-indicator, .spinner, .topic-loading');
|
|
1538
|
+
} catch (e) { return false; }
|
|
1561
1539
|
}
|
|
1562
1540
|
|
|
1563
|
-
function
|
|
1564
|
-
// Always ensure UL/OL doesn't directly contain DIV wraps.
|
|
1541
|
+
function ensureHostForWrap(wrap, ul) {
|
|
1565
1542
|
try {
|
|
1566
1543
|
if (!wrap || wrap.nodeType !== 1) return null;
|
|
1567
|
-
if (!(wrap.matches && wrap.matches(
|
|
1568
|
-
|
|
1569
|
-
var ul = getListContainer(wrap);
|
|
1570
|
-
if (!ul) return null;
|
|
1571
|
-
|
|
1572
|
-
var host = wrap.closest ? wrap.closest('li.' + HOST_CLASS) : null;
|
|
1544
|
+
if (!(wrap.matches && wrap.matches(WRAP_SEL))) return null;
|
|
1545
|
+
var host = wrap.closest ? wrap.closest(HOST_SEL) : null;
|
|
1573
1546
|
if (host) return host;
|
|
1574
|
-
|
|
1575
|
-
|
|
1547
|
+
ul = ul || (wrap.closest ? wrap.closest('ul,ol') : null);
|
|
1548
|
+
if (!ul) return null;
|
|
1576
1549
|
if (wrap.parentElement === ul) {
|
|
1577
1550
|
host = document.createElement('li');
|
|
1578
|
-
host.className =
|
|
1551
|
+
host.className = 'nodebb-ezoic-host';
|
|
1579
1552
|
host.setAttribute('role', 'listitem');
|
|
1580
1553
|
host.style.listStyle = 'none';
|
|
1581
1554
|
host.style.width = '100%';
|
|
1582
|
-
|
|
1583
1555
|
ul.insertBefore(host, wrap);
|
|
1584
1556
|
host.appendChild(wrap);
|
|
1585
1557
|
try { wrap.style.width = '100%'; } catch (e) {}
|
|
@@ -1589,118 +1561,177 @@ function buildOrdinalMap(items) {
|
|
|
1589
1561
|
return null;
|
|
1590
1562
|
}
|
|
1591
1563
|
|
|
1592
|
-
function
|
|
1593
|
-
// Only called on upscroll to avoid breaking load logic.
|
|
1564
|
+
function nthTopic(ul, n) {
|
|
1594
1565
|
try {
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
if (
|
|
1566
|
+
var topics = ul.querySelectorAll(TOPIC_LI_SEL);
|
|
1567
|
+
if (!topics || !topics.length) return null;
|
|
1568
|
+
if (n < 1) n = 1;
|
|
1569
|
+
if (n > topics.length) n = topics.length;
|
|
1570
|
+
return topics[n-1] || null;
|
|
1571
|
+
} catch (e) { return null; }
|
|
1572
|
+
}
|
|
1598
1573
|
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1574
|
+
function previousTopicLi(host) {
|
|
1575
|
+
try {
|
|
1576
|
+
var prev = host.previousElementSibling;
|
|
1577
|
+
while (prev) {
|
|
1578
|
+
if (prev.matches && prev.matches(TOPIC_LI_SEL)) return prev;
|
|
1579
|
+
prev = prev.previousElementSibling;
|
|
1605
1580
|
}
|
|
1581
|
+
} catch (e) {}
|
|
1582
|
+
return null;
|
|
1583
|
+
}
|
|
1606
1584
|
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1585
|
+
function placeHost(host, ul) {
|
|
1586
|
+
try {
|
|
1587
|
+
if (!host || host.nodeType !== 1) return;
|
|
1588
|
+
ul = ul || host.parentElement;
|
|
1589
|
+
if (!ul) return;
|
|
1590
|
+
|
|
1591
|
+
var wrap = host.querySelector && host.querySelector(WRAP_SEL);
|
|
1592
|
+
if (!wrap) { host.remove(); return; }
|
|
1593
|
+
|
|
1594
|
+
var after = wrap.getAttribute('data-ezoic-after');
|
|
1595
|
+
var anchor = null;
|
|
1596
|
+
|
|
1597
|
+
if (after) {
|
|
1598
|
+
anchor = nthTopic(ul, parseInt(after, 10));
|
|
1599
|
+
}
|
|
1600
|
+
if (!anchor) {
|
|
1601
|
+
anchor = previousTopicLi(host);
|
|
1602
|
+
}
|
|
1603
|
+
if (!anchor) {
|
|
1604
|
+
// Can't place reliably; if host drifted to top area, remove to avoid pile-up.
|
|
1605
|
+
// Otherwise keep as-is.
|
|
1606
|
+
var firstTopic = nthTopic(ul, 1);
|
|
1607
|
+
if (firstTopic && host !== firstTopic && host.compareDocumentPosition(firstTopic) & Node.DOCUMENT_POSITION_FOLLOWING) {
|
|
1608
|
+
host.remove();
|
|
1609
|
+
}
|
|
1610
|
+
return;
|
|
1611
1611
|
}
|
|
1612
|
-
host.setAttribute(TOKEN_ATTR, t);
|
|
1613
|
-
host.removeAttribute('data-ezoic-orphan');
|
|
1614
1612
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
anchorLi.insertAdjacentElement('afterend', host);
|
|
1613
|
+
if (host.previousElementSibling !== anchor) {
|
|
1614
|
+
anchor.insertAdjacentElement('afterend', host);
|
|
1618
1615
|
}
|
|
1619
1616
|
} catch (e) {}
|
|
1620
1617
|
}
|
|
1621
1618
|
|
|
1622
|
-
function
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
} catch (e) {}
|
|
1632
|
-
|
|
1633
|
-
// If NOT upscroll, do nothing else (prevents "ads accumulate at bottom" and avoids scroll blocking)
|
|
1634
|
-
if (dy > -8) return;
|
|
1619
|
+
function drain() {
|
|
1620
|
+
pending = false;
|
|
1621
|
+
if (isLoading()) {
|
|
1622
|
+
// retry later
|
|
1623
|
+
scheduleDrain(250);
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
var ul = getTopicList();
|
|
1627
|
+
if (!ul) return;
|
|
1635
1628
|
|
|
1636
|
-
//
|
|
1629
|
+
// repair invalid ul>div once
|
|
1637
1630
|
try {
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
if (!host) host = w.closest ? w.closest('li.' + HOST_CLASS) : null;
|
|
1642
|
-
var ul = getListContainer(host || w);
|
|
1643
|
-
if (host && ul) tetherHostToAnchor(host, ul);
|
|
1644
|
-
});
|
|
1645
|
-
|
|
1646
|
-
// Drop orphans only on upscroll
|
|
1647
|
-
var orphans = document.querySelectorAll('li.' + HOST_CLASS + '[data-ezoic-orphan="1"]');
|
|
1648
|
-
orphans.forEach(function(h){ try { h.remove(); } catch(e) {} });
|
|
1649
|
-
|
|
1650
|
-
// Drop empty hosts
|
|
1651
|
-
var empties = document.querySelectorAll('li.' + HOST_CLASS);
|
|
1652
|
-
empties.forEach(function(h){
|
|
1653
|
-
try { if (!(h.querySelector && h.querySelector(BETWEEN_SEL))) h.remove(); } catch(e) {}
|
|
1631
|
+
ul.querySelectorAll(':scope > ' + WRAP_SEL).forEach(function(w){
|
|
1632
|
+
var h = ensureHostForWrap(w, ul);
|
|
1633
|
+
if (h) queue.add(h);
|
|
1654
1634
|
});
|
|
1655
1635
|
} catch (e) {}
|
|
1636
|
+
|
|
1637
|
+
var count = 0;
|
|
1638
|
+
for (var host of Array.from(queue)) {
|
|
1639
|
+
queue.delete(host);
|
|
1640
|
+
placeHost(host, ul);
|
|
1641
|
+
count++;
|
|
1642
|
+
if (count >= 8) break; // keep it light
|
|
1643
|
+
}
|
|
1644
|
+
if (queue.size) scheduleDrain(60);
|
|
1656
1645
|
}
|
|
1657
1646
|
|
|
1658
|
-
function
|
|
1659
|
-
var now = Date.now();
|
|
1660
|
-
if (now - lastRun < COOLDOWN) return;
|
|
1647
|
+
function scheduleDrain(delay) {
|
|
1661
1648
|
if (pending) return;
|
|
1662
1649
|
pending = true;
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
}
|
|
1650
|
+
if (delay) {
|
|
1651
|
+
setTimeout(function(){ requestAnimationFrame(drain); }, delay);
|
|
1652
|
+
} else {
|
|
1653
|
+
requestAnimationFrame(drain);
|
|
1654
|
+
}
|
|
1668
1655
|
}
|
|
1669
1656
|
|
|
1670
|
-
function
|
|
1671
|
-
// Initial repair only
|
|
1657
|
+
function enqueueFromNode(node) {
|
|
1672
1658
|
try {
|
|
1673
|
-
|
|
1674
|
-
|
|
1659
|
+
if (!node || node.nodeType !== 1) return;
|
|
1660
|
+
var ul = getTopicList();
|
|
1661
|
+
if (!ul) return;
|
|
1662
|
+
|
|
1663
|
+
if (node.matches && node.matches(WRAP_SEL)) {
|
|
1664
|
+
var h = ensureHostForWrap(node, ul) || (node.closest ? node.closest(HOST_SEL) : null);
|
|
1665
|
+
if (h) queue.add(h);
|
|
1666
|
+
} else if (node.matches && node.matches(HOST_SEL)) {
|
|
1667
|
+
queue.add(node);
|
|
1668
|
+
} else if (node.querySelectorAll) {
|
|
1669
|
+
node.querySelectorAll(WRAP_SEL).forEach(function(w){
|
|
1670
|
+
var h2 = ensureHostForWrap(w, ul) || (w.closest ? w.closest(HOST_SEL) : null);
|
|
1671
|
+
if (h2) queue.add(h2);
|
|
1672
|
+
});
|
|
1673
|
+
node.querySelectorAll(HOST_SEL).forEach(function(h3){ queue.add(h3); });
|
|
1674
|
+
}
|
|
1675
1675
|
} catch (e) {}
|
|
1676
|
+
}
|
|
1676
1677
|
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1678
|
+
function init() {
|
|
1679
|
+
// initial scan (light)
|
|
1680
|
+
try {
|
|
1681
|
+
var ul = getTopicList();
|
|
1682
|
+
if (ul) {
|
|
1683
|
+
ul.querySelectorAll(WRAP_SEL).forEach(function(w){
|
|
1684
|
+
var h = ensureHostForWrap(w, ul) || (w.closest ? w.closest(HOST_SEL) : null);
|
|
1685
|
+
if (h) queue.add(h);
|
|
1686
|
+
});
|
|
1687
|
+
scheduleDrain(0);
|
|
1688
|
+
}
|
|
1689
|
+
} catch (e) {}
|
|
1687
1690
|
|
|
1688
|
-
//
|
|
1691
|
+
// Observe ONLY the topic list container once available
|
|
1689
1692
|
try {
|
|
1690
1693
|
if (typeof MutationObserver !== 'undefined') {
|
|
1691
1694
|
var mo = new MutationObserver(function(muts){
|
|
1692
1695
|
for (var i=0;i<muts.length;i++){
|
|
1693
|
-
var m=muts[i];
|
|
1694
|
-
if (
|
|
1696
|
+
var m = muts[i];
|
|
1697
|
+
if (m.addedNodes && m.addedNodes.length) {
|
|
1698
|
+
for (var j=0;j<m.addedNodes.length;j++){
|
|
1699
|
+
enqueueFromNode(m.addedNodes[j]);
|
|
1700
|
+
}
|
|
1701
|
+
scheduleDrain(0);
|
|
1702
|
+
}
|
|
1695
1703
|
}
|
|
1696
1704
|
});
|
|
1697
|
-
|
|
1705
|
+
|
|
1706
|
+
var ul = getTopicList();
|
|
1707
|
+
if (ul) mo.observe(ul, { childList:true, subtree:true });
|
|
1708
|
+
else {
|
|
1709
|
+
// wait for ajaxify
|
|
1710
|
+
var mo2 = new MutationObserver(function(){
|
|
1711
|
+
var u2 = getTopicList();
|
|
1712
|
+
if (u2) {
|
|
1713
|
+
try { mo.observe(u2, { childList:true, subtree:true }); } catch(e){}
|
|
1714
|
+
try { mo2.disconnect(); } catch(e){}
|
|
1715
|
+
}
|
|
1716
|
+
});
|
|
1717
|
+
mo2.observe(document.documentElement || document.body, { childList:true, subtree:true });
|
|
1718
|
+
}
|
|
1698
1719
|
}
|
|
1699
1720
|
} catch (e) {}
|
|
1721
|
+
|
|
1722
|
+
if (window.jQuery) {
|
|
1723
|
+
try {
|
|
1724
|
+
window.jQuery(window).on('action:infiniteScroll.loaded action:ajaxify.end', function(){
|
|
1725
|
+
freezeUntil = now() + 900; // allow NodeBB to settle
|
|
1726
|
+
// then reconcile any drift
|
|
1727
|
+
setTimeout(function(){ scheduleDrain(0); }, 950);
|
|
1728
|
+
});
|
|
1729
|
+
} catch (e) {}
|
|
1730
|
+
}
|
|
1700
1731
|
}
|
|
1701
1732
|
|
|
1702
1733
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
|
|
1703
1734
|
else init();
|
|
1704
1735
|
})();
|
|
1705
|
-
// ===== /V14.1.
|
|
1736
|
+
// ===== /V14.1.3 =====
|
|
1706
1737
|
|
package/public/style.css
CHANGED
package/README.md
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# NodeBB Plugin – Ezoic Infinite (Production)
|
|
2
|
-
|
|
3
|
-
This plugin injects Ezoic placeholders between topics and posts on NodeBB 4.x,
|
|
4
|
-
with full support for infinite scroll.
|
|
5
|
-
|
|
6
|
-
## Key guarantees
|
|
7
|
-
- No duplicate ads back-to-back
|
|
8
|
-
- One showAds call per placeholder
|
|
9
|
-
- Fast reveal (MutationObserver on first child)
|
|
10
|
-
- Safe with ajaxify navigation
|
|
11
|
-
- Works with NodeBB 4.x + Harmony
|
|
12
|
-
|
|
13
|
-
## Notes
|
|
14
|
-
- Placeholders must exist and be selected in Ezoic
|
|
15
|
-
- Use separate ID pools for topics vs messages
|