nodebb-plugin-ezoic-infinite 1.6.33 → 1.6.35
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 +178 -118
- package/public/style.css +4 -6
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -1514,162 +1514,222 @@ function buildOrdinalMap(items) {
|
|
|
1514
1514
|
|
|
1515
1515
|
|
|
1516
1516
|
|
|
1517
|
-
// =====
|
|
1518
|
-
(function(){
|
|
1517
|
+
// ===== V18: Always host between DIVs in <li> + anchor by topic tid (MutationObserver, no scroll hooks to injection) =====
|
|
1518
|
+
(function () {
|
|
1519
1519
|
var TOPIC_LI_SEL = 'li[component="category/topic"]';
|
|
1520
1520
|
var BETWEEN_WRAP_SEL = 'div.nodebb-ezoic-wrap.ezoic-ad-between';
|
|
1521
1521
|
var HOST_CLASS = 'nodebb-ezoic-host';
|
|
1522
|
-
var
|
|
1523
|
-
|
|
1524
|
-
var scheduled = false;
|
|
1525
|
-
var lastRun = 0;
|
|
1526
|
-
var COOLDOWN = 70;
|
|
1522
|
+
var ANCHOR_ATTR = 'data-ezoic-anchor-tid';
|
|
1527
1523
|
|
|
1528
|
-
var
|
|
1524
|
+
var pending = false;
|
|
1525
|
+
var queue = new Set();
|
|
1529
1526
|
|
|
1530
|
-
function
|
|
1531
|
-
|
|
1532
|
-
function getTopicList(){
|
|
1533
|
-
try{
|
|
1527
|
+
function getTopicList() {
|
|
1528
|
+
try {
|
|
1534
1529
|
var li = document.querySelector(TOPIC_LI_SEL);
|
|
1535
1530
|
if (!li) return null;
|
|
1536
1531
|
return li.closest ? li.closest('ul,ol') : null;
|
|
1537
|
-
}catch(e){ return null; }
|
|
1532
|
+
} catch (e) { return null; }
|
|
1538
1533
|
}
|
|
1539
1534
|
|
|
1540
|
-
function
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
if (
|
|
1544
|
-
|
|
1545
|
-
|
|
1535
|
+
function extractTidFromTopicLi(li) {
|
|
1536
|
+
// Parse tid from a topic link (/topic/{tid}/...)
|
|
1537
|
+
try {
|
|
1538
|
+
if (!li || !li.querySelector) return null;
|
|
1539
|
+
var a = li.querySelector('a[href^="/topic/"], a[href*="/topic/"]');
|
|
1540
|
+
if (!a) return null;
|
|
1541
|
+
var href = a.getAttribute('href') || '';
|
|
1542
|
+
var m = href.match(/\/topic\/(\d+)\b/);
|
|
1543
|
+
return m ? m[1] : null;
|
|
1544
|
+
} catch (e) { return null; }
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
function buildTidMap(ul) {
|
|
1548
|
+
var map = Object.create(null);
|
|
1549
|
+
try {
|
|
1550
|
+
var topics = ul.querySelectorAll(TOPIC_LI_SEL);
|
|
1551
|
+
for (var i = 0; i < topics.length; i++) {
|
|
1552
|
+
var tid = extractTidFromTopicLi(topics[i]);
|
|
1553
|
+
if (tid && !map[tid]) map[tid] = topics[i];
|
|
1546
1554
|
}
|
|
1547
|
-
}catch(e){}
|
|
1548
|
-
return
|
|
1555
|
+
} catch (e) {}
|
|
1556
|
+
return map;
|
|
1549
1557
|
}
|
|
1550
1558
|
|
|
1551
|
-
function
|
|
1552
|
-
try
|
|
1559
|
+
function previousTopicLi(node) {
|
|
1560
|
+
try {
|
|
1561
|
+
var prev = node.previousElementSibling;
|
|
1562
|
+
while (prev) {
|
|
1563
|
+
if (prev.matches && prev.matches(TOPIC_LI_SEL)) return prev;
|
|
1564
|
+
prev = prev.previousElementSibling;
|
|
1565
|
+
}
|
|
1566
|
+
} catch (e) {}
|
|
1567
|
+
return null;
|
|
1553
1568
|
}
|
|
1554
1569
|
|
|
1555
|
-
function
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
if (
|
|
1559
|
-
|
|
1560
|
-
|
|
1570
|
+
function ensureHostForWrap(wrap, ul) {
|
|
1571
|
+
// Always ensure UL children are LI, to avoid NodeBB reparenting DIVs (root cause of pile-up).
|
|
1572
|
+
try {
|
|
1573
|
+
if (!wrap || wrap.nodeType !== 1) return null;
|
|
1574
|
+
if (!(wrap.matches && wrap.matches(BETWEEN_WRAP_SEL))) return null;
|
|
1575
|
+
|
|
1576
|
+
var host = wrap.closest ? wrap.closest('li.' + HOST_CLASS) : null;
|
|
1577
|
+
if (host) return host;
|
|
1578
|
+
|
|
1579
|
+
ul = ul || (wrap.closest ? wrap.closest('ul,ol') : null);
|
|
1580
|
+
if (!ul || !(ul.tagName === 'UL' || ul.tagName === 'OL')) return null;
|
|
1581
|
+
|
|
1582
|
+
// Only wrap if it's a direct child of the list
|
|
1583
|
+
if (wrap.parentElement === ul) {
|
|
1584
|
+
host = document.createElement('li');
|
|
1585
|
+
host.className = HOST_CLASS;
|
|
1586
|
+
host.setAttribute('role', 'listitem');
|
|
1587
|
+
host.style.listStyle = 'none';
|
|
1588
|
+
host.style.width = '100%';
|
|
1589
|
+
|
|
1590
|
+
// anchor tid from previous topic
|
|
1591
|
+
var anchor = previousTopicLi(wrap) || wrap.previousElementSibling;
|
|
1592
|
+
if (anchor && anchor.matches && anchor.matches(TOPIC_LI_SEL)) {
|
|
1593
|
+
var tid = extractTidFromTopicLi(anchor);
|
|
1594
|
+
if (tid) host.setAttribute(ANCHOR_ATTR, tid);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
ul.insertBefore(host, wrap);
|
|
1598
|
+
host.appendChild(wrap);
|
|
1599
|
+
try { wrap.style.width = '100%'; } catch (e) {}
|
|
1600
|
+
return host;
|
|
1601
|
+
}
|
|
1602
|
+
} catch (e) {}
|
|
1603
|
+
return null;
|
|
1561
1604
|
}
|
|
1562
1605
|
|
|
1563
|
-
function
|
|
1564
|
-
try{
|
|
1565
|
-
if (!
|
|
1566
|
-
|
|
1567
|
-
|
|
1606
|
+
function ensureAnchorTid(host) {
|
|
1607
|
+
try {
|
|
1608
|
+
if (!host || host.nodeType !== 1) return;
|
|
1609
|
+
if (host.getAttribute(ANCHOR_ATTR)) return;
|
|
1610
|
+
var anchor = previousTopicLi(host);
|
|
1611
|
+
if (!anchor) return;
|
|
1612
|
+
var tid = extractTidFromTopicLi(anchor);
|
|
1613
|
+
if (tid) host.setAttribute(ANCHOR_ATTR, tid);
|
|
1614
|
+
} catch (e) {}
|
|
1568
1615
|
}
|
|
1569
1616
|
|
|
1570
|
-
function
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1617
|
+
function reconcileHost(host, ul, tidMap) {
|
|
1618
|
+
try {
|
|
1619
|
+
if (!host || host.nodeType !== 1 || !host.isConnected) return;
|
|
1620
|
+
ul = ul || host.parentElement;
|
|
1574
1621
|
if (!ul) return;
|
|
1575
|
-
var vh = window.innerHeight || 800;
|
|
1576
|
-
var bandTop = -200; // slightly above viewport
|
|
1577
|
-
var bandBottom = vh * 1.6; // covers top area user sees when returning up
|
|
1578
|
-
|
|
1579
|
-
// collect candidates in band in DOM order
|
|
1580
|
-
var kids = ul.children;
|
|
1581
|
-
var candidates = [];
|
|
1582
|
-
for (var i=0;i<kids.length;i++){
|
|
1583
|
-
var el = kids[i];
|
|
1584
|
-
if (!isBetweenNode(el) && !isTopicNode(el)) continue;
|
|
1585
|
-
// compute rect (cheap enough on limited band; stop after we've passed far below band)
|
|
1586
|
-
var r = el.getBoundingClientRect ? el.getBoundingClientRect() : null;
|
|
1587
|
-
if (!r) continue;
|
|
1588
|
-
if (r.top > bandBottom && candidates.length > 0) {
|
|
1589
|
-
// Once we've started collecting and we are past band, we can stop scanning further.
|
|
1590
|
-
break;
|
|
1591
|
-
}
|
|
1592
|
-
if (r.bottom < bandTop) continue;
|
|
1593
|
-
if (r.top > bandBottom) continue;
|
|
1594
1622
|
|
|
1595
|
-
|
|
1596
|
-
|
|
1623
|
+
// if host got detached from list, skip
|
|
1624
|
+
if (!(ul.tagName === 'UL' || ul.tagName === 'OL')) return;
|
|
1597
1625
|
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
setPiled(el2, run >= 2);
|
|
1609
|
-
}
|
|
1626
|
+
ensureAnchorTid(host);
|
|
1627
|
+
|
|
1628
|
+
var tid = host.getAttribute(ANCHOR_ATTR);
|
|
1629
|
+
if (!tid) return;
|
|
1630
|
+
|
|
1631
|
+
var anchorLi = tidMap[tid];
|
|
1632
|
+
if (!anchorLi || !anchorLi.isConnected) return;
|
|
1633
|
+
|
|
1634
|
+
if (host.previousElementSibling !== anchorLi) {
|
|
1635
|
+
anchorLi.insertAdjacentElement('afterend', host);
|
|
1610
1636
|
}
|
|
1611
|
-
}catch(e){}
|
|
1637
|
+
} catch (e) {}
|
|
1612
1638
|
}
|
|
1613
1639
|
|
|
1614
|
-
function
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
if (
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1640
|
+
function drain() {
|
|
1641
|
+
pending = false;
|
|
1642
|
+
var ul = getTopicList();
|
|
1643
|
+
if (!ul) return;
|
|
1644
|
+
|
|
1645
|
+
// First, host any direct-child between DIVs currently in list (cheap, keeps DOM valid).
|
|
1646
|
+
try {
|
|
1647
|
+
ul.querySelectorAll(':scope > ' + BETWEEN_WRAP_SEL).forEach(function (w) {
|
|
1648
|
+
var h = ensureHostForWrap(w, ul);
|
|
1649
|
+
if (h) queue.add(h);
|
|
1650
|
+
});
|
|
1651
|
+
} catch (e) {}
|
|
1652
|
+
|
|
1653
|
+
var tidMap = buildTidMap(ul);
|
|
1654
|
+
|
|
1655
|
+
// Process a limited batch per frame
|
|
1656
|
+
var processed = 0;
|
|
1657
|
+
for (var host of Array.from(queue)) {
|
|
1658
|
+
queue.delete(host);
|
|
1659
|
+
reconcileHost(host, ul, tidMap);
|
|
1660
|
+
processed++;
|
|
1661
|
+
if (processed >= 12) break;
|
|
1662
|
+
}
|
|
1663
|
+
if (queue.size) schedule();
|
|
1624
1664
|
}
|
|
1625
1665
|
|
|
1626
|
-
function
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1666
|
+
function schedule() {
|
|
1667
|
+
if (pending) return;
|
|
1668
|
+
pending = true;
|
|
1669
|
+
requestAnimationFrame(drain);
|
|
1670
|
+
}
|
|
1630
1671
|
|
|
1672
|
+
function enqueueNode(node, ul) {
|
|
1673
|
+
try {
|
|
1674
|
+
if (!node || node.nodeType !== 1) return;
|
|
1675
|
+
if (node.matches && node.matches(BETWEEN_WRAP_SEL)) {
|
|
1676
|
+
var h = ensureHostForWrap(node, ul);
|
|
1677
|
+
if (h) queue.add(h);
|
|
1678
|
+
} else if (node.tagName === 'LI' && node.classList && node.classList.contains(HOST_CLASS)) {
|
|
1679
|
+
queue.add(node);
|
|
1680
|
+
} else if (node.querySelectorAll) {
|
|
1681
|
+
// only check within added subtree, not whole doc
|
|
1682
|
+
node.querySelectorAll(':scope ' + BETWEEN_WRAP_SEL).forEach(function (w) {
|
|
1683
|
+
var h2 = ensureHostForWrap(w, ul);
|
|
1684
|
+
if (h2) queue.add(h2);
|
|
1685
|
+
});
|
|
1686
|
+
node.querySelectorAll(':scope li.' + HOST_CLASS).forEach(function (h3) { queue.add(h3); });
|
|
1687
|
+
}
|
|
1688
|
+
} catch (e) {}
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
function init() {
|
|
1631
1692
|
var ul = getTopicList();
|
|
1632
1693
|
if (!ul) return;
|
|
1633
1694
|
|
|
1634
|
-
//
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1695
|
+
// Initial pass: host all between DIVs and reconcile once.
|
|
1696
|
+
schedule();
|
|
1697
|
+
|
|
1698
|
+
// Observe only the topic list
|
|
1699
|
+
if (typeof MutationObserver !== 'undefined') {
|
|
1700
|
+
var mo = new MutationObserver(function (muts) {
|
|
1701
|
+
try {
|
|
1702
|
+
var u = getTopicList();
|
|
1703
|
+
if (!u) return;
|
|
1704
|
+
|
|
1705
|
+
for (var i = 0; i < muts.length; i++) {
|
|
1706
|
+
var m = muts[i];
|
|
1707
|
+
if (m.addedNodes && m.addedNodes.length) {
|
|
1708
|
+
for (var j = 0; j < m.addedNodes.length; j++) {
|
|
1709
|
+
enqueueNode(m.addedNodes[j], u);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
if (queue.size) schedule();
|
|
1714
|
+
} catch (e) {}
|
|
1715
|
+
});
|
|
1716
|
+
try { mo.observe(ul, { childList: true, subtree: true }); } catch (e) {}
|
|
1640
1717
|
}
|
|
1641
|
-
}
|
|
1642
1718
|
|
|
1643
|
-
|
|
1644
|
-
window.
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
if (window.jQuery){
|
|
1652
|
-
try{
|
|
1653
|
-
window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
|
|
1654
|
-
setTimeout(function(){
|
|
1655
|
-
var ul = getTopicList();
|
|
1656
|
-
if (!ul) return;
|
|
1657
|
-
schedule(function(){ maskViewportPile(ul); });
|
|
1658
|
-
}, 120);
|
|
1719
|
+
// NodeBB events: run a settle pass after batches
|
|
1720
|
+
if (window.jQuery) {
|
|
1721
|
+
try {
|
|
1722
|
+
window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function () {
|
|
1723
|
+
// Let NodeBB append topics, then reconcile hosts by anchor tid.
|
|
1724
|
+
setTimeout(function(){ schedule(); }, 120);
|
|
1725
|
+
setTimeout(function(){ schedule(); }, 520);
|
|
1659
1726
|
});
|
|
1660
|
-
}catch(e){}
|
|
1727
|
+
} catch (e) {}
|
|
1661
1728
|
}
|
|
1662
|
-
|
|
1663
|
-
// initial
|
|
1664
|
-
setTimeout(function(){
|
|
1665
|
-
var ul = getTopicList();
|
|
1666
|
-
if (!ul) return;
|
|
1667
|
-
schedule(function(){ maskViewportPile(ul); });
|
|
1668
|
-
}, 250);
|
|
1669
1729
|
}
|
|
1670
1730
|
|
|
1671
1731
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
|
|
1672
1732
|
else init();
|
|
1673
1733
|
})();
|
|
1674
|
-
// ===== /
|
|
1734
|
+
// ===== /V18 =====
|
|
1675
1735
|
|
package/public/style.css
CHANGED
|
@@ -88,10 +88,8 @@ li.nodebb-ezoic-host > .nodebb-ezoic-wrap.ezoic-ad-between { width: 100%; displa
|
|
|
88
88
|
|
|
89
89
|
|
|
90
90
|
|
|
91
|
-
/* =====
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
li.nodebb-ezoic-host > .nodebb-ezoic-wrap.ezoic-ad-between { width:100%; display:block; }
|
|
96
|
-
/* ===== /V17.5 ===== */
|
|
91
|
+
/* ===== V18 hosts ===== */
|
|
92
|
+
li.nodebb-ezoic-host{ list-style:none; width:100%; display:block; }
|
|
93
|
+
li.nodebb-ezoic-host > .nodebb-ezoic-wrap.ezoic-ad-between{ width:100%; display:block; }
|
|
94
|
+
/* ===== /V18 ===== */
|
|
97
95
|
|