nodebb-plugin-ezoic-infinite 1.8.95 → 1.8.97
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/.claude/settings.local.json +4 -1
- package/library.js +14 -9
- package/package.json +1 -1
- package/public/client.js +87 -32
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
"WebFetch(domain:registry.npmjs.org)",
|
|
5
5
|
"Bash(curl -sL \"https://registry.npmjs.org/nodebb-plugin-ezoic-infinite/-/nodebb-plugin-ezoic-infinite-1.8.81.tgz\" -o /tmp/ezoic-1.8.81.tgz && tar -tzf /tmp/ezoic-1.8.81.tgz)",
|
|
6
6
|
"Bash(tar -xzf /tmp/ezoic-1.8.81.tgz -C /tmp/ package/library.js package/public/client.js package/package.json)",
|
|
7
|
-
"Bash(cd /tmp && curl -sL \"https://registry.npmjs.org/nodebb-plugin-ezoic-infinite/-/nodebb-plugin-ezoic-infinite-1.8.81.tgz\" -o ezoic-1.8.81.tgz && tar -xzf ezoic-1.8.81.tgz && echo \"Extracted OK\")"
|
|
7
|
+
"Bash(cd /tmp && curl -sL \"https://registry.npmjs.org/nodebb-plugin-ezoic-infinite/-/nodebb-plugin-ezoic-infinite-1.8.81.tgz\" -o ezoic-1.8.81.tgz && tar -xzf ezoic-1.8.81.tgz && echo \"Extracted OK\")",
|
|
8
|
+
"Bash(node -e \"require\\(''./library.js''\\)\")",
|
|
9
|
+
"Bash(node --check \"C:\\\\Users\\\\arnau\\\\OneDrive\\\\Dev\\\\nodebb-plugin-onekite-ezoic\\\\public\\\\client.js\")",
|
|
10
|
+
"Bash(node --check \"C:\\\\Users\\\\arnau\\\\OneDrive\\\\Dev\\\\nodebb-plugin-onekite-ezoic\\\\library.js\")"
|
|
8
11
|
]
|
|
9
12
|
}
|
|
10
13
|
}
|
package/library.js
CHANGED
|
@@ -29,17 +29,25 @@ function parseBool(v, def = false) {
|
|
|
29
29
|
return s === '1' || s === 'true' || s === 'on' || s === 'yes';
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
let _allGroupsCache = null;
|
|
33
|
+
let _allGroupsCacheAt = 0;
|
|
34
|
+
const ALL_GROUPS_TTL = 300_000; // 5 min — admin-only route
|
|
35
|
+
|
|
32
36
|
async function getAllGroups() {
|
|
37
|
+
const t = Date.now();
|
|
38
|
+
if (_allGroupsCache && (t - _allGroupsCacheAt) < ALL_GROUPS_TTL) return _allGroupsCache;
|
|
33
39
|
try {
|
|
34
40
|
let names = await db.getSortedSetRange('groups:createtime', 0, -1);
|
|
35
41
|
if (!names?.length) {
|
|
36
42
|
names = await db.getSortedSetRange('groups:visible:createtime', 0, -1);
|
|
37
43
|
}
|
|
38
|
-
|
|
44
|
+
_allGroupsCache = (await groups.getGroupsData(
|
|
39
45
|
(names || []).filter(name => !groups.isPrivilegeGroup(name))
|
|
40
46
|
))
|
|
41
47
|
.filter(g => g?.name)
|
|
42
48
|
.sort((a, b) => String(a.name).localeCompare(String(b.name), undefined, { sensitivity: 'base' }));
|
|
49
|
+
_allGroupsCacheAt = Date.now();
|
|
50
|
+
return _allGroupsCache;
|
|
43
51
|
} catch (err) {
|
|
44
52
|
console.error('[ezoic-infinite] getAllGroups error:', err.message);
|
|
45
53
|
return [];
|
|
@@ -93,15 +101,10 @@ async function isUserExcluded(uid, excludedGroups) {
|
|
|
93
101
|
|
|
94
102
|
_excludeCache.set(key, { value, at: Date.now() });
|
|
95
103
|
if (_excludeCache.size > 1000) {
|
|
96
|
-
let
|
|
97
|
-
const t = Date.now();
|
|
98
|
-
for (const [k, v] of _excludeCache) {
|
|
99
|
-
if (!toDel) break;
|
|
100
|
-
if (t - v.at >= EXCLUDE_TTL) { _excludeCache.delete(k); toDel--; }
|
|
101
|
-
}
|
|
104
|
+
let n = 100;
|
|
102
105
|
for (const k of _excludeCache.keys()) {
|
|
103
|
-
if (!
|
|
104
|
-
_excludeCache.delete(k);
|
|
106
|
+
if (!n--) break;
|
|
107
|
+
_excludeCache.delete(k);
|
|
105
108
|
}
|
|
106
109
|
}
|
|
107
110
|
|
|
@@ -117,6 +120,8 @@ function clearCaches() {
|
|
|
117
120
|
_excludeCache.clear();
|
|
118
121
|
_inlineCfgNormal = null;
|
|
119
122
|
_inlineCfgExcluded = null;
|
|
123
|
+
_allGroupsCache = null;
|
|
124
|
+
_allGroupsCacheAt = 0;
|
|
120
125
|
}
|
|
121
126
|
|
|
122
127
|
// ── Client config ────────────────────────────────────────────────────────────
|
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
SHOW_TIMEOUT_MS: 4_500,
|
|
35
35
|
SHOW_RELEASE_MS: 700,
|
|
36
36
|
RECYCLE_DELAY_MS: 300,
|
|
37
|
-
UNCOLLAPSE_CHECK_MS: [500,
|
|
37
|
+
UNCOLLAPSE_CHECK_MS: [500, 5_000],
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
const MAX_INSERTS_RUN = 6;
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
pageKey: null,
|
|
82
82
|
kind: null,
|
|
83
83
|
cfg: null,
|
|
84
|
+
opts: null,
|
|
84
85
|
poolsReady: false,
|
|
85
86
|
pools: { topics: [], posts: [], categories: [] },
|
|
86
87
|
cursors: { topics: 0, posts: 0, categories: 0 },
|
|
@@ -104,6 +105,12 @@
|
|
|
104
105
|
|
|
105
106
|
const isBlocked = () => now() < S.blockedUntil;
|
|
106
107
|
|
|
108
|
+
// ── Posts cache (burst-scoped) ─────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
let _postsCache = null;
|
|
111
|
+
let _postsCacheTs = 0;
|
|
112
|
+
const POSTS_CACHE_MS = 200;
|
|
113
|
+
|
|
107
114
|
function mutate(fn) {
|
|
108
115
|
S.mutGuard++;
|
|
109
116
|
try { fn(); } finally { S.mutGuard--; }
|
|
@@ -132,6 +139,17 @@
|
|
|
132
139
|
S.pools.topics = parseIds(cfg.placeholderIds);
|
|
133
140
|
S.pools.posts = parseIds(cfg.messagePlaceholderIds);
|
|
134
141
|
S.pools.categories = parseIds(cfg.categoryPlaceholderIds);
|
|
142
|
+
S.opts = {
|
|
143
|
+
enableBetweenAds: normBool(cfg.enableBetweenAds),
|
|
144
|
+
showFirstTopicAd: normBool(cfg.showFirstTopicAd),
|
|
145
|
+
intervalTopics: Math.max(1, parseInt(cfg.intervalPosts, 10) || 3),
|
|
146
|
+
enableCategoryAds: normBool(cfg.enableCategoryAds),
|
|
147
|
+
showFirstCategoryAd: normBool(cfg.showFirstCategoryAd),
|
|
148
|
+
intervalCategories: Math.max(1, parseInt(cfg.intervalCategories, 10) || 3),
|
|
149
|
+
enableMessageAds: normBool(cfg.enableMessageAds),
|
|
150
|
+
showFirstMessageAd: normBool(cfg.showFirstMessageAd),
|
|
151
|
+
messageInterval: Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3),
|
|
152
|
+
};
|
|
135
153
|
S.poolsReady = true;
|
|
136
154
|
}
|
|
137
155
|
|
|
@@ -164,17 +182,22 @@
|
|
|
164
182
|
// ── DOM queries ────────────────────────────────────────────────────────────
|
|
165
183
|
|
|
166
184
|
function getPosts() {
|
|
185
|
+
const t = now();
|
|
186
|
+
if (_postsCache && t - _postsCacheTs < POSTS_CACHE_MS) return _postsCache;
|
|
167
187
|
const all = document.querySelectorAll(SEL.post);
|
|
168
188
|
const out = [];
|
|
169
189
|
for (let i = 0; i < all.length; i++) {
|
|
170
190
|
const el = all[i];
|
|
171
191
|
if (!el.isConnected) continue;
|
|
192
|
+
if (el.childElementCount === 0) continue;
|
|
172
193
|
if (!el.querySelector('[component="post/content"]')) continue;
|
|
173
194
|
const parent = el.parentElement?.closest(SEL.post);
|
|
174
195
|
if (parent && parent !== el) continue;
|
|
175
196
|
if (el.getAttribute('component') === 'post/parent') continue;
|
|
176
197
|
out.push(el);
|
|
177
198
|
}
|
|
199
|
+
_postsCache = out;
|
|
200
|
+
_postsCacheTs = t;
|
|
178
201
|
return out;
|
|
179
202
|
}
|
|
180
203
|
|
|
@@ -243,17 +266,14 @@
|
|
|
243
266
|
const cfg = KIND[klass];
|
|
244
267
|
if (!cfg) return false;
|
|
245
268
|
const parent = wrap.parentElement;
|
|
246
|
-
if (parent)
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
269
|
+
if (!parent) return false;
|
|
270
|
+
const sel = `${cfg.baseTag || ''}[${cfg.anchorAttr}="${anchorId}"]`;
|
|
271
|
+
for (const sib of parent.children) {
|
|
272
|
+
if (sib !== wrap) {
|
|
273
|
+
try { if (sib.matches(sel)) return sib.isConnected; } catch (_) {}
|
|
252
274
|
}
|
|
253
275
|
}
|
|
254
|
-
|
|
255
|
-
return document.querySelector(`${cfg.sel}[${cfg.anchorAttr}="${anchorId}"]`)?.isConnected === true;
|
|
256
|
-
} catch (_) { return false; }
|
|
276
|
+
return false;
|
|
257
277
|
}
|
|
258
278
|
|
|
259
279
|
const adjacentWrap = el => wrapIsLive(el.nextElementSibling) || wrapIsLive(el.previousElementSibling);
|
|
@@ -476,9 +496,12 @@
|
|
|
476
496
|
|
|
477
497
|
// ── IntersectionObserver ───────────────────────────────────────────────────
|
|
478
498
|
|
|
499
|
+
let _ioMobile = null;
|
|
500
|
+
|
|
479
501
|
function getIO() {
|
|
480
502
|
if (S.io) return S.io;
|
|
481
503
|
try {
|
|
504
|
+
_ioMobile = isMobile();
|
|
482
505
|
S.io = new IntersectionObserver(entries => {
|
|
483
506
|
for (const e of entries) {
|
|
484
507
|
if (!e.isIntersecting) continue;
|
|
@@ -488,7 +511,7 @@
|
|
|
488
511
|
}
|
|
489
512
|
}, {
|
|
490
513
|
root: null,
|
|
491
|
-
rootMargin:
|
|
514
|
+
rootMargin: _ioMobile ? IO_MARGIN_MOBILE : IO_MARGIN_DESKTOP,
|
|
492
515
|
threshold: 0,
|
|
493
516
|
});
|
|
494
517
|
} catch (_) { S.io = null; }
|
|
@@ -621,18 +644,19 @@
|
|
|
621
644
|
const kind = getKind();
|
|
622
645
|
if (kind === 'other') return 0;
|
|
623
646
|
|
|
624
|
-
const exec = (klass, getItems,
|
|
625
|
-
if (!
|
|
626
|
-
return injectBetween(klass, getItems(),
|
|
647
|
+
const exec = (klass, getItems, enable, interval, showFirst, poolKey) => {
|
|
648
|
+
if (!enable) return 0;
|
|
649
|
+
return injectBetween(klass, getItems(), interval, showFirst, poolKey);
|
|
627
650
|
};
|
|
628
651
|
|
|
652
|
+
const o = S.opts;
|
|
629
653
|
if (kind === 'topic')
|
|
630
|
-
return exec('ezoic-ad-message', getPosts,
|
|
654
|
+
return exec('ezoic-ad-message', getPosts, o.enableMessageAds, o.messageInterval, o.showFirstMessageAd, 'posts');
|
|
631
655
|
if (kind === 'categoryTopics') {
|
|
632
656
|
pruneOrphansBetween();
|
|
633
|
-
return exec('ezoic-ad-between', getTopics,
|
|
657
|
+
return exec('ezoic-ad-between', getTopics, o.enableBetweenAds, o.intervalTopics, o.showFirstTopicAd, 'topics');
|
|
634
658
|
}
|
|
635
|
-
return exec('ezoic-ad-categories', getCategories,
|
|
659
|
+
return exec('ezoic-ad-categories', getCategories, o.enableCategoryAds, o.intervalCategories, o.showFirstCategoryAd, 'categories');
|
|
636
660
|
}
|
|
637
661
|
|
|
638
662
|
// ── Scheduler ──────────────────────────────────────────────────────────────
|
|
@@ -686,8 +710,10 @@
|
|
|
686
710
|
for (const timers of S.wrapTimers.values()) { for (const t of timers) clearTimeout(t); }
|
|
687
711
|
S.wrapTimers.clear();
|
|
688
712
|
S.domObs?.disconnect(); S.domObs = null;
|
|
689
|
-
S.io?.disconnect(); S.io = null;
|
|
713
|
+
S.io?.disconnect(); S.io = null; _ioMobile = null;
|
|
714
|
+
_postsCache = null; _postsCacheTs = 0;
|
|
690
715
|
S.cfg = null;
|
|
716
|
+
S.opts = null;
|
|
691
717
|
S.poolsReady = false;
|
|
692
718
|
S.pools = { topics: [], posts: [], categories: [] };
|
|
693
719
|
S.cursors = { topics: 0, posts: 0, categories: 0 };
|
|
@@ -728,16 +754,21 @@
|
|
|
728
754
|
for (const node of m.addedNodes) {
|
|
729
755
|
if (!(node instanceof Element)) continue;
|
|
730
756
|
try {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
757
|
+
// Check closest first (cheap) before doing FILL_SEL querySelector on every added node
|
|
758
|
+
const emptyWrap = node.closest?.(`.${WRAP_CLASS}.is-empty`) ?? m.target?.closest?.(`.${WRAP_CLASS}.is-empty`);
|
|
759
|
+
if (emptyWrap && (node.matches?.(FILL_SEL) || node.querySelector?.(FILL_SEL))) {
|
|
760
|
+
clearEmptyIfFilled(emptyWrap);
|
|
734
761
|
}
|
|
735
762
|
} catch (_) {}
|
|
736
763
|
try {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
764
|
+
if (node.classList?.contains(WRAP_CLASS)) {
|
|
765
|
+
const id = parseInt(node.getAttribute(ATTR.WRAPID), 10);
|
|
766
|
+
if (id > 0) { const ph = node.querySelector(`[id^="${PH_PREFIX}"]`); if (ph) try { getIO()?.observe(ph); } catch (_) {} }
|
|
767
|
+
} else if (node.childElementCount > 0) {
|
|
768
|
+
for (const wrap of node.querySelectorAll(`.${WRAP_CLASS}`)) {
|
|
769
|
+
const id = parseInt(wrap.getAttribute(ATTR.WRAPID), 10);
|
|
770
|
+
if (id > 0) { const ph = wrap.querySelector(`[id^="${PH_PREFIX}"]`); if (ph) try { getIO()?.observe(ph); } catch (_) {} }
|
|
771
|
+
}
|
|
741
772
|
}
|
|
742
773
|
} catch (_) {}
|
|
743
774
|
if (!needsBurst) {
|
|
@@ -747,9 +778,12 @@
|
|
|
747
778
|
}
|
|
748
779
|
}
|
|
749
780
|
}
|
|
750
|
-
if (needsBurst) requestBurst();
|
|
781
|
+
if (needsBurst) { _postsCacheTs = 0; requestBurst(); }
|
|
751
782
|
});
|
|
752
|
-
try {
|
|
783
|
+
try {
|
|
784
|
+
const target = document.getElementById('content') || document.body;
|
|
785
|
+
S.domObs.observe(target, { childList: true, subtree: true });
|
|
786
|
+
} catch (_) {}
|
|
753
787
|
}
|
|
754
788
|
|
|
755
789
|
// ── TCF / CMP Protection ───────────────────────────────────────────────────
|
|
@@ -810,6 +844,8 @@
|
|
|
810
844
|
S.pageKey = pageKey(); S.kind = null; S.blockedUntil = 0;
|
|
811
845
|
ensureTcfLocator(); protectAriaHidden();
|
|
812
846
|
patchShowAds(); getIO(); ensureDomObserver();
|
|
847
|
+
RETRY.count = 0; RETRY.scriptReloaded = false; RETRY.postReloadShown = false; RETRY.gen++;
|
|
848
|
+
setTimeout(() => retryBoot(RETRY.gen), 250);
|
|
813
849
|
requestBurst();
|
|
814
850
|
});
|
|
815
851
|
// action:ajaxify.contentLoaded et action:category.loaded ne passent pas par hooks → jQuery uniquement
|
|
@@ -821,7 +857,7 @@
|
|
|
821
857
|
try {
|
|
822
858
|
require(['hooks'], hooks => {
|
|
823
859
|
if (typeof hooks?.on !== 'function') return;
|
|
824
|
-
for (const ev of ['action:
|
|
860
|
+
for (const ev of ['action:posts.loaded', 'action:topics.loaded', 'action:categories.loaded', 'action:topic.loaded'])
|
|
825
861
|
try { hooks.on(ev, () => { if (!isBlocked()) requestBurst(); }); } catch (_) {}
|
|
826
862
|
});
|
|
827
863
|
} catch (_) {}
|
|
@@ -835,6 +871,21 @@
|
|
|
835
871
|
}, { passive: true });
|
|
836
872
|
}
|
|
837
873
|
|
|
874
|
+
function bindResize() {
|
|
875
|
+
let timer = null;
|
|
876
|
+
window.addEventListener('resize', () => {
|
|
877
|
+
clearTimeout(timer);
|
|
878
|
+
timer = setTimeout(() => {
|
|
879
|
+
if (!S.io || _ioMobile === null) return;
|
|
880
|
+
const m = isMobile();
|
|
881
|
+
if (m === _ioMobile) return;
|
|
882
|
+
S.io.disconnect(); S.io = null; _ioMobile = null;
|
|
883
|
+
getIO();
|
|
884
|
+
for (const id of S.mountedIds) observePh(id);
|
|
885
|
+
}, 250);
|
|
886
|
+
}, { passive: true });
|
|
887
|
+
}
|
|
888
|
+
|
|
838
889
|
// ── Boot ───────────────────────────────────────────────────────────────────
|
|
839
890
|
|
|
840
891
|
S.pageKey = pageKey();
|
|
@@ -845,6 +896,7 @@
|
|
|
845
896
|
ensureDomObserver();
|
|
846
897
|
bindNodeBB();
|
|
847
898
|
bindScroll();
|
|
899
|
+
bindResize();
|
|
848
900
|
S.blockedUntil = 0;
|
|
849
901
|
if (document.readyState === 'complete') {
|
|
850
902
|
requestBurst();
|
|
@@ -862,10 +914,13 @@
|
|
|
862
914
|
// in ez-standalone.js → onStandaloneLoadEvent crash). After ~6s, remove and reload
|
|
863
915
|
// sa.min.js; Ezoic's partial first-run state may let the second run succeed.
|
|
864
916
|
// Once recovered, re-enqueue all mounted placeholders so showAds() fires.
|
|
865
|
-
const RETRY = { count: 0, scriptReloaded: false, postReloadShown: false };
|
|
866
|
-
function retryBoot() {
|
|
917
|
+
const RETRY = { count: 0, scriptReloaded: false, postReloadShown: false, gen: 0 };
|
|
918
|
+
function retryBoot(gen) {
|
|
919
|
+
if (gen !== RETRY.gen) return;
|
|
867
920
|
if (RETRY.count >= 12) return;
|
|
868
921
|
RETRY.count++;
|
|
922
|
+
// On pages that never show ads, no need to retry (unless mid-reload recovery)
|
|
923
|
+
if (!RETRY.scriptReloaded && getKind() === 'other') return;
|
|
869
924
|
patchShowAds();
|
|
870
925
|
|
|
871
926
|
const ez = window.ezstandalone;
|
|
@@ -916,8 +971,8 @@
|
|
|
916
971
|
S.lastBurstTs = now() - TIMING.BURST_COOLDOWN_MS;
|
|
917
972
|
requestBurst();
|
|
918
973
|
}
|
|
919
|
-
setTimeout(retryBoot, RETRY.count <= 4 ? 300 : 1000);
|
|
974
|
+
setTimeout(() => retryBoot(gen), RETRY.count <= 4 ? 300 : 1000);
|
|
920
975
|
}
|
|
921
|
-
setTimeout(retryBoot, 250);
|
|
976
|
+
setTimeout(() => retryBoot(RETRY.gen), 250);
|
|
922
977
|
|
|
923
978
|
})();
|