nodebb-plugin-ezoic-infinite 1.6.17 → 1.6.19
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 +190 -143
- package/public/style.css +2 -2
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -1,116 +1,6 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
function ezoicAnchorToken() {
|
|
5
|
-
// cheap unique-ish token
|
|
6
|
-
try { return String(Date.now()) + '-' + Math.random().toString(16).slice(2); } catch (e) { return String(Date.now()); }
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function ezoicInsertAfterWithToken(target, wrap, kindClass) {
|
|
10
|
-
try {
|
|
11
|
-
if (!target || !wrap) return;
|
|
12
|
-
|
|
13
|
-
// Tag the anchor LI (topic item) with a stable token the first time we attach a between ad to it.
|
|
14
|
-
// If NodeBB re-renders/virtualizes and recreates the LI, this tag disappears => we can drop orphan ads.
|
|
15
|
-
var anchorLi = null;
|
|
16
|
-
try { anchorLi = target.closest ? target.closest('li') : null; } catch (e) {}
|
|
17
|
-
if (!anchorLi && target.tagName === 'LI') anchorLi = target;
|
|
18
|
-
|
|
19
|
-
if (kindClass === 'ezoic-ad-between' && anchorLi) {
|
|
20
|
-
var token = anchorLi.getAttribute('data-ezoic-anchor-token');
|
|
21
|
-
if (!token) {
|
|
22
|
-
token = ezoicAnchorToken();
|
|
23
|
-
try { anchorLi.setAttribute('data-ezoic-anchor-token', token); } catch (e) {}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// If inside UL/OL, keep valid DOM with LI host, but keep wrap as DIV (compat).
|
|
27
|
-
var p = anchorLi.parentElement;
|
|
28
|
-
if (p && (p.tagName === 'UL' || p.tagName === 'OL')) {
|
|
29
|
-
var host = document.createElement('li');
|
|
30
|
-
host.className = 'nodebb-ezoic-host';
|
|
31
|
-
host.setAttribute('role', 'listitem');
|
|
32
|
-
host.style.listStyle = 'none';
|
|
33
|
-
host.style.width = '100%';
|
|
34
|
-
try { host.setAttribute('data-ezoic-anchor-token', token); } catch (e) {}
|
|
35
|
-
|
|
36
|
-
if (anchorLi.insertAdjacentElement) anchorLi.insertAdjacentElement('afterend', host);
|
|
37
|
-
else if (p.insertBefore) p.insertBefore(host, anchorLi.nextSibling);
|
|
38
|
-
|
|
39
|
-
try { host.appendChild(wrap); } catch (e) {}
|
|
40
|
-
try { wrap.style.width = '100%'; } catch (e) {}
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Non-list parent: just insert sibling but still tag wrap so we can reconcile
|
|
45
|
-
try { wrap.setAttribute('data-ezoic-anchor-token', token); } catch (e) {}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (target.insertAdjacentElement) ezoicInsertAfterWithToken(target, wrap, kindClass);
|
|
49
|
-
else if (target.parentNode) target.parentNode.insertBefore(wrap, target.nextSibling);
|
|
50
|
-
} catch (e) {}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function ezoicRepairBetweenWrapsToHost() {
|
|
54
|
-
// Repair any invalid UL children and ensure hosts/wraps carry token if possible.
|
|
55
|
-
try {
|
|
56
|
-
var bad = document.querySelectorAll('ul > div.nodebb-ezoic-wrap.ezoic-ad-between, ol > div.nodebb-ezoic-wrap.ezoic-ad-between');
|
|
57
|
-
bad.forEach(function (wrap) {
|
|
58
|
-
try {
|
|
59
|
-
var ul = wrap.parentElement;
|
|
60
|
-
if (!ul) return;
|
|
61
|
-
var prevLi = wrap.previousElementSibling && wrap.previousElementSibling.tagName === 'LI' ? wrap.previousElementSibling : null;
|
|
62
|
-
var token = prevLi ? prevLi.getAttribute('data-ezoic-anchor-token') : null;
|
|
63
|
-
if (!token && prevLi) {
|
|
64
|
-
token = ezoicAnchorToken();
|
|
65
|
-
prevLi.setAttribute('data-ezoic-anchor-token', token);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
var host = document.createElement('li');
|
|
69
|
-
host.className = 'nodebb-ezoic-host';
|
|
70
|
-
host.setAttribute('role', 'listitem');
|
|
71
|
-
host.style.listStyle = 'none';
|
|
72
|
-
host.style.width = '100%';
|
|
73
|
-
if (token) host.setAttribute('data-ezoic-anchor-token', token);
|
|
74
|
-
|
|
75
|
-
ul.insertBefore(host, wrap);
|
|
76
|
-
host.appendChild(wrap);
|
|
77
|
-
wrap.style.width = '100%';
|
|
78
|
-
} catch (e) {}
|
|
79
|
-
});
|
|
80
|
-
} catch (e) {}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function ezoicReconcileOrDropHostsByToken(scope) {
|
|
84
|
-
// Core fix: if NodeBB virtualizes and anchors disappear, DROP hosts so they can't pile up at top.
|
|
85
|
-
// If anchor exists, ensure host sits right after it.
|
|
86
|
-
try {
|
|
87
|
-
var root = scope || document;
|
|
88
|
-
var hosts = root.querySelectorAll('li.nodebb-ezoic-host[data-ezoic-anchor-token]');
|
|
89
|
-
if (!hosts || !hosts.length) return;
|
|
90
|
-
|
|
91
|
-
hosts.forEach(function(host){
|
|
92
|
-
try {
|
|
93
|
-
var token = host.getAttribute('data-ezoic-anchor-token');
|
|
94
|
-
if (!token) return;
|
|
95
|
-
var listEl = host.parentElement;
|
|
96
|
-
if (!listEl) return;
|
|
97
|
-
|
|
98
|
-
// Find the exact anchor LI that still carries the token.
|
|
99
|
-
var anchor = listEl.querySelector('li[data-ezoic-anchor-token="' + token + '"]');
|
|
100
|
-
if (!anchor) {
|
|
101
|
-
// anchor not in DOM => virtualized/re-rendered: remove host to avoid stacking
|
|
102
|
-
host.remove();
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
if (host.previousElementSibling !== anchor) {
|
|
106
|
-
anchor.insertAdjacentElement('afterend', host);
|
|
107
|
-
}
|
|
108
|
-
} catch (e) {}
|
|
109
|
-
});
|
|
110
|
-
} catch (e) {}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
4
|
// Track scroll direction to avoid aggressive recycling when the user scrolls upward.
|
|
115
5
|
// Recycling while scrolling up is a common cause of ads "bunching" and a "disappearing too fast" feeling.
|
|
116
6
|
let lastScrollY = 0;
|
|
@@ -778,7 +668,7 @@ function globalGapFixInit() {
|
|
|
778
668
|
insertingIds.add(id);
|
|
779
669
|
try {
|
|
780
670
|
const wrap = buildWrap(id, kindClass, afterPos, !existingPh);
|
|
781
|
-
|
|
671
|
+
target.insertAdjacentElement('afterend', wrap);
|
|
782
672
|
|
|
783
673
|
// If placeholder exists elsewhere (including pool), move it into the wrapper.
|
|
784
674
|
if (existingPh) {
|
|
@@ -1270,7 +1160,7 @@ function buildOrdinalMap(items) {
|
|
|
1270
1160
|
if (!anchorEl || !wrap || !wrap.isConnected) return null;
|
|
1271
1161
|
|
|
1272
1162
|
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
1273
|
-
|
|
1163
|
+
anchorEl.insertAdjacentElement('afterend', wrap);
|
|
1274
1164
|
|
|
1275
1165
|
// Ensure minimal layout impact.
|
|
1276
1166
|
try { wrap.style.contain = 'layout style paint'; } catch (e) {}
|
|
@@ -1620,49 +1510,206 @@ function buildOrdinalMap(items) {
|
|
|
1620
1510
|
})();
|
|
1621
1511
|
|
|
1622
1512
|
|
|
1623
|
-
|
|
1624
|
-
(
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
});
|
|
1513
|
+
|
|
1514
|
+
// ===== V14.2 targeted hook (topic list only) + upscroll reconcile =====
|
|
1515
|
+
(function () {
|
|
1516
|
+
var BETWEEN_SEL = 'div.nodebb-ezoic-wrap.ezoic-ad-between';
|
|
1517
|
+
var HOST_CLASS = 'nodebb-ezoic-host';
|
|
1518
|
+
var TOKEN_ATTR = 'data-ezoic-anchor-token';
|
|
1519
|
+
var TOPIC_LI_SEL = 'li[component="category/topic"]';
|
|
1520
|
+
|
|
1521
|
+
var lastY = window.pageYOffset || document.documentElement.scrollTop || 0;
|
|
1522
|
+
|
|
1523
|
+
function token() {
|
|
1524
|
+
try { return String(Date.now()) + '-' + Math.random().toString(16).slice(2); } catch (e) { return String(Date.now()); }
|
|
1636
1525
|
}
|
|
1637
1526
|
|
|
1638
|
-
|
|
1639
|
-
|
|
1527
|
+
function getScrollY() {
|
|
1528
|
+
return window.pageYOffset || document.documentElement.scrollTop || 0;
|
|
1529
|
+
}
|
|
1640
1530
|
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
setTimeout(function(){ schedule(document); }, 0);
|
|
1644
|
-
setTimeout(function(){ schedule(document); }, 220);
|
|
1645
|
-
setTimeout(function(){ schedule(document); }, 800);
|
|
1646
|
-
});
|
|
1531
|
+
function isTopicList(ul) {
|
|
1532
|
+
try { return !!(ul && ul.querySelector && ul.querySelector(TOPIC_LI_SEL)); } catch (e) { return false; }
|
|
1647
1533
|
}
|
|
1648
1534
|
|
|
1649
|
-
|
|
1650
|
-
|
|
1535
|
+
function closestTopicList(node) {
|
|
1536
|
+
try {
|
|
1537
|
+
var ul = node && node.closest ? node.closest('ul,ol') : null;
|
|
1538
|
+
if (ul && isTopicList(ul)) return ul;
|
|
1539
|
+
// sometimes wrap is outside: search up
|
|
1540
|
+
var p = node && node.parentElement;
|
|
1541
|
+
while (p) {
|
|
1542
|
+
if ((p.tagName === 'UL' || p.tagName === 'OL') && isTopicList(p)) return p;
|
|
1543
|
+
p = p.parentElement;
|
|
1544
|
+
}
|
|
1545
|
+
} catch (e) {}
|
|
1546
|
+
return null;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
function closestAnchorTopicLi(hostOrWrap, ul) {
|
|
1550
|
+
try {
|
|
1551
|
+
var cur = hostOrWrap;
|
|
1552
|
+
if (!cur) return null;
|
|
1553
|
+
// if wrap inside host li, start from host
|
|
1554
|
+
if (cur.closest) {
|
|
1555
|
+
var h = cur.closest('li.' + HOST_CLASS);
|
|
1556
|
+
if (h) cur = h;
|
|
1557
|
+
}
|
|
1558
|
+
var prev = cur.previousElementSibling;
|
|
1559
|
+
while (prev) {
|
|
1560
|
+
if (prev.matches && prev.matches(TOPIC_LI_SEL)) return prev;
|
|
1561
|
+
prev = prev.previousElementSibling;
|
|
1562
|
+
}
|
|
1563
|
+
} catch (e) {}
|
|
1564
|
+
return null;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
function ensureHostAndTokenForWrap(wrap) {
|
|
1568
|
+
try {
|
|
1569
|
+
if (!wrap || wrap.nodeType !== 1) return;
|
|
1570
|
+
if (!(wrap.matches && wrap.matches(BETWEEN_SEL))) return;
|
|
1571
|
+
|
|
1572
|
+
var ul = closestTopicList(wrap);
|
|
1573
|
+
if (!ul) return;
|
|
1574
|
+
|
|
1575
|
+
// Ensure wrap is not direct child of UL/OL
|
|
1576
|
+
var host = wrap.closest ? wrap.closest('li.' + HOST_CLASS) : null;
|
|
1577
|
+
if (!host) {
|
|
1578
|
+
if (wrap.parentElement === ul) {
|
|
1579
|
+
host = document.createElement('li');
|
|
1580
|
+
host.className = HOST_CLASS;
|
|
1581
|
+
host.setAttribute('role', 'listitem');
|
|
1582
|
+
host.style.listStyle = 'none';
|
|
1583
|
+
host.style.width = '100%';
|
|
1584
|
+
ul.insertBefore(host, wrap);
|
|
1585
|
+
host.appendChild(wrap);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
if (!host) return;
|
|
1590
|
+
|
|
1591
|
+
// Anchor = previous topic LI only
|
|
1592
|
+
var anchorLi = closestAnchorTopicLi(host, ul);
|
|
1593
|
+
if (!anchorLi) {
|
|
1594
|
+
host.setAttribute('data-ezoic-orphan', '1');
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
var t = anchorLi.getAttribute(TOKEN_ATTR);
|
|
1599
|
+
if (!t) {
|
|
1600
|
+
t = token();
|
|
1601
|
+
anchorLi.setAttribute(TOKEN_ATTR, t);
|
|
1602
|
+
}
|
|
1603
|
+
host.setAttribute(TOKEN_ATTR, t);
|
|
1604
|
+
host.removeAttribute('data-ezoic-orphan');
|
|
1605
|
+
} catch (e) {}
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
function repairInvalidUlChildren(ul) {
|
|
1609
|
+
try {
|
|
1610
|
+
if (!ul) return;
|
|
1611
|
+
// Fix ul>div wraps
|
|
1612
|
+
var bad = ul.querySelectorAll(':scope > ' + BETWEEN_SEL);
|
|
1613
|
+
bad.forEach(function(wrap){ ensureHostAndTokenForWrap(wrap); });
|
|
1614
|
+
} catch (e) {}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
function cleanupEmptyHosts(ul) {
|
|
1618
|
+
try {
|
|
1619
|
+
if (!ul) return;
|
|
1620
|
+
var hosts = ul.querySelectorAll('li.' + HOST_CLASS);
|
|
1621
|
+
hosts.forEach(function(host){
|
|
1622
|
+
try {
|
|
1623
|
+
var hasWrap = host.querySelector && host.querySelector(BETWEEN_SEL);
|
|
1624
|
+
if (!hasWrap) host.remove();
|
|
1625
|
+
} catch (e) {}
|
|
1626
|
+
});
|
|
1627
|
+
} catch (e) {}
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
function reconcileUpScroll(ul) {
|
|
1631
|
+
// Only reconcile (move/drop) on upscroll to avoid breaking infinite scroll load calculations.
|
|
1632
|
+
try {
|
|
1633
|
+
if (!ul) return;
|
|
1634
|
+
var y = getScrollY();
|
|
1635
|
+
var dy = y - lastY;
|
|
1636
|
+
lastY = y;
|
|
1637
|
+
if (dy >= -10) return; // not an upscroll
|
|
1638
|
+
|
|
1639
|
+
// Reconcile / drop
|
|
1640
|
+
var hosts = ul.querySelectorAll('li.' + HOST_CLASS + '[' + TOKEN_ATTR + '], li.' + HOST_CLASS + '[data-ezoic-orphan="1"]');
|
|
1641
|
+
hosts.forEach(function(host){
|
|
1642
|
+
try {
|
|
1643
|
+
if (host.getAttribute('data-ezoic-orphan') === '1') { host.remove(); return; }
|
|
1644
|
+
var t = host.getAttribute(TOKEN_ATTR);
|
|
1645
|
+
if (!t) { host.remove(); return; }
|
|
1646
|
+
var anchor = ul.querySelector(TOPIC_LI_SEL + '[' + TOKEN_ATTR + '="' + t + '"]');
|
|
1647
|
+
if (!anchor) { host.remove(); return; }
|
|
1648
|
+
if (host.previousElementSibling !== anchor) anchor.insertAdjacentElement('afterend', host);
|
|
1649
|
+
} catch (e) {}
|
|
1650
|
+
});
|
|
1651
|
+
} catch (e) {}
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
function sweepAll(reasonNode) {
|
|
1655
|
+
try {
|
|
1656
|
+
var ul = closestTopicList(reasonNode || document.querySelector(TOPIC_LI_SEL));
|
|
1657
|
+
if (!ul) return;
|
|
1658
|
+
repairInvalidUlChildren(ul);
|
|
1659
|
+
|
|
1660
|
+
// Tokenize any new wraps within this list
|
|
1661
|
+
ul.querySelectorAll(BETWEEN_SEL).forEach(function(wrap){ ensureHostAndTokenForWrap(wrap); });
|
|
1662
|
+
|
|
1663
|
+
// Don't move on downscroll; only clean empties
|
|
1664
|
+
cleanupEmptyHosts(ul);
|
|
1665
|
+
reconcileUpScroll(ul);
|
|
1666
|
+
} catch (e) {}
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
// Process mutations incrementally (no full-document scan)
|
|
1670
|
+
function installMO() {
|
|
1671
|
+
try {
|
|
1672
|
+
if (typeof MutationObserver === 'undefined') return;
|
|
1651
1673
|
var mo = new MutationObserver(function(muts){
|
|
1652
1674
|
for (var i=0;i<muts.length;i++){
|
|
1653
|
-
var m=muts[i];
|
|
1654
|
-
if (
|
|
1655
|
-
|
|
1675
|
+
var m = muts[i];
|
|
1676
|
+
if (m.addedNodes && m.addedNodes.length) {
|
|
1677
|
+
for (var j=0;j<m.addedNodes.length;j++){
|
|
1678
|
+
var n = m.addedNodes[j];
|
|
1679
|
+
if (!n || n.nodeType !== 1) continue;
|
|
1680
|
+
if (n.matches && n.matches(BETWEEN_SEL)) {
|
|
1681
|
+
ensureHostAndTokenForWrap(n);
|
|
1682
|
+
} else if (n.querySelector && n.querySelector(BETWEEN_SEL)) {
|
|
1683
|
+
n.querySelectorAll(BETWEEN_SEL).forEach(function(w){ ensureHostAndTokenForWrap(w); });
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
// light sweep on the container only
|
|
1687
|
+
sweepAll(m.target);
|
|
1656
1688
|
break;
|
|
1657
1689
|
}
|
|
1658
1690
|
}
|
|
1659
1691
|
});
|
|
1660
|
-
document.
|
|
1661
|
-
|
|
1692
|
+
mo.observe(document.documentElement || document.body, { childList: true, subtree: true });
|
|
1693
|
+
} catch (e) {}
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
function init() {
|
|
1697
|
+
// initial tokenizing/repair
|
|
1698
|
+
sweepAll(document);
|
|
1699
|
+
window.addEventListener('scroll', function(){ sweepAll(document); }, { passive: true });
|
|
1700
|
+
window.addEventListener('resize', function(){ sweepAll(document); }, { passive: true });
|
|
1701
|
+
|
|
1702
|
+
if (window.jQuery) {
|
|
1703
|
+
window.jQuery(window).on('action:ajaxify.end action:infiniteScroll.loaded', function(){
|
|
1704
|
+
setTimeout(function(){ sweepAll(document); }, 0);
|
|
1705
|
+
setTimeout(function(){ sweepAll(document); }, 250);
|
|
1662
1706
|
});
|
|
1663
1707
|
}
|
|
1664
|
-
|
|
1708
|
+
installMO();
|
|
1709
|
+
}
|
|
1665
1710
|
|
|
1666
|
-
|
|
1711
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
|
|
1712
|
+
else init();
|
|
1667
1713
|
})();
|
|
1668
|
-
// ===== /V14 =====
|
|
1714
|
+
// ===== /V14.2 =====
|
|
1715
|
+
|