nodebb-plugin-ezoic-infinite 0.6.2 → 0.6.3
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 +111 -89
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -5,13 +5,33 @@ window.ezoicInfiniteLoaded = true;
|
|
|
5
5
|
|
|
6
6
|
let cachedConfig;
|
|
7
7
|
let lastFetch = 0;
|
|
8
|
-
|
|
9
8
|
let debounceTimer;
|
|
10
|
-
let lastSignature = null;
|
|
11
9
|
|
|
12
10
|
let inFlight = false;
|
|
13
11
|
let rerunRequested = false;
|
|
14
12
|
|
|
13
|
+
// Incremental state (prevents ads "jumping to the top")
|
|
14
|
+
let pageKey = null;
|
|
15
|
+
let injectedSlots = new Set(); // slotNumber per page
|
|
16
|
+
let usedIds = new Set(); // ids currently injected per page
|
|
17
|
+
|
|
18
|
+
function resetPageState() {
|
|
19
|
+
injectedSlots = new Set();
|
|
20
|
+
usedIds = new Set();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function currentPageKey() {
|
|
24
|
+
// Stable key per ajaxified page
|
|
25
|
+
try {
|
|
26
|
+
if (ajaxify && ajaxify.data) {
|
|
27
|
+
if (ajaxify.data.tid) return 'topic:' + ajaxify.data.tid;
|
|
28
|
+
if (ajaxify.data.cid) return 'category:' + ajaxify.data.cid;
|
|
29
|
+
if (ajaxify.data.template) return 'tpl:' + ajaxify.data.template + ':' + (ajaxify.data.url || window.location.pathname);
|
|
30
|
+
}
|
|
31
|
+
} catch (e) {}
|
|
32
|
+
return window.location.pathname;
|
|
33
|
+
}
|
|
34
|
+
|
|
15
35
|
function parsePool(raw) {
|
|
16
36
|
if (!raw) return [];
|
|
17
37
|
return Array.from(new Set(
|
|
@@ -29,7 +49,6 @@ async function fetchConfig() {
|
|
|
29
49
|
return cachedConfig;
|
|
30
50
|
}
|
|
31
51
|
|
|
32
|
-
// Robust DOM-based detection (no dependency on ajaxify template timing)
|
|
33
52
|
function isTopicPage() {
|
|
34
53
|
return $('[component="post/content"]').length > 0 || $('[component="post"][data-pid]').length > 0;
|
|
35
54
|
}
|
|
@@ -42,7 +61,6 @@ function getTopicPosts() {
|
|
|
42
61
|
const $primary = $('[component="post"][data-pid]');
|
|
43
62
|
if ($primary.length) return $primary.not('.ezoic-ad-post');
|
|
44
63
|
|
|
45
|
-
// Fallback: top-level data-pid containing post/content
|
|
46
64
|
return $('[data-pid]').filter(function () {
|
|
47
65
|
const $el = $(this);
|
|
48
66
|
const hasContent = $el.find('[component="post/content"]').length > 0;
|
|
@@ -59,83 +77,22 @@ function tagName($el) {
|
|
|
59
77
|
return ($el && $el.length ? (($el.prop('tagName') || '').toUpperCase()) : '');
|
|
60
78
|
}
|
|
61
79
|
|
|
62
|
-
function makeWrapperLike($target, classes, innerHtml) {
|
|
80
|
+
function makeWrapperLike($target, classes, innerHtml, attrs) {
|
|
63
81
|
const t = tagName($target);
|
|
82
|
+
const attrStr = attrs ? ' ' + attrs : '';
|
|
64
83
|
if (t === 'LI') {
|
|
65
|
-
return '<li class="' + classes + ' list-unstyled"
|
|
84
|
+
return '<li class="' + classes + ' list-unstyled"' + attrStr + '>' + innerHtml + '</li>';
|
|
66
85
|
}
|
|
67
|
-
return '<div class="' + classes + '"
|
|
86
|
+
return '<div class="' + classes + '"' + attrStr + '>' + innerHtml + '</div>';
|
|
68
87
|
}
|
|
69
88
|
|
|
70
|
-
function
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
const start = Math.max(1, slots - poolSize + 1);
|
|
74
|
-
const out = [];
|
|
75
|
-
for (let s = start; s <= slots; s++) out.push(s);
|
|
76
|
-
return out;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function removeOurWrappers() {
|
|
80
|
-
$('.ezoic-ad-post').remove();
|
|
81
|
-
$('.ezoic-ad-between').remove();
|
|
82
|
-
$('.ezoic-ad-topic').remove();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function insertBetween($items, ids, interval, wrapperClass) {
|
|
86
|
-
const total = $items.length;
|
|
87
|
-
const slotsToRender = computeWindowSlots(total, interval, ids.length);
|
|
88
|
-
if (!slotsToRender.length) return [];
|
|
89
|
-
|
|
90
|
-
const activeIds = [];
|
|
91
|
-
for (let i = 0; i < slotsToRender.length; i++) {
|
|
92
|
-
const slotNumber = slotsToRender[i];
|
|
93
|
-
const id = ids[i];
|
|
94
|
-
const index = slotNumber * interval - 1;
|
|
95
|
-
const $target = $items.eq(index);
|
|
96
|
-
if (!$target.length) continue;
|
|
97
|
-
|
|
98
|
-
const html = makeWrapperLike($target, wrapperClass, '<div id="ezoic-pub-ad-placeholder-' + id + '"></div>');
|
|
99
|
-
$target.after(html);
|
|
100
|
-
activeIds.push(id);
|
|
89
|
+
function pickNextId(pool) {
|
|
90
|
+
for (const id of pool) {
|
|
91
|
+
if (!usedIds.has(id)) return id;
|
|
101
92
|
}
|
|
102
|
-
return
|
|
93
|
+
return null;
|
|
103
94
|
}
|
|
104
95
|
|
|
105
|
-
function insertMessageAds($posts, ids, interval) {
|
|
106
|
-
const total = $posts.length;
|
|
107
|
-
const slotsToRender = computeWindowSlots(total, interval, ids.length);
|
|
108
|
-
if (!slotsToRender.length) return [];
|
|
109
|
-
|
|
110
|
-
const activeIds = [];
|
|
111
|
-
for (let i = 0; i < slotsToRender.length; i++) {
|
|
112
|
-
const slotNumber = slotsToRender[i];
|
|
113
|
-
const id = ids[i];
|
|
114
|
-
const index = slotNumber * interval - 1;
|
|
115
|
-
const $target = $posts.eq(index);
|
|
116
|
-
if (!$target.length) continue;
|
|
117
|
-
|
|
118
|
-
const inner = '<div class="content"><div id="ezoic-pub-ad-placeholder-' + id + '"></div></div>';
|
|
119
|
-
const html = makeWrapperLike($target, 'post ezoic-ad-post', inner);
|
|
120
|
-
$target.after(html);
|
|
121
|
-
activeIds.push(id);
|
|
122
|
-
}
|
|
123
|
-
return activeIds;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function signatureFor($posts, $topicItems) {
|
|
127
|
-
if ($posts.length) {
|
|
128
|
-
const lastPid = $posts.last().attr('data-pid') || '';
|
|
129
|
-
return 'topic:' + $posts.length + ':' + lastPid;
|
|
130
|
-
}
|
|
131
|
-
if ($topicItems.length) {
|
|
132
|
-
const lastTid = $topicItems.last().attr('data-tid') || '';
|
|
133
|
-
return 'category:' + $topicItems.length + ':' + lastTid;
|
|
134
|
-
}
|
|
135
|
-
return 'none';
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Call Ezoic when ready (retry a few times, non-destructive)
|
|
139
96
|
function callEzoic(ids) {
|
|
140
97
|
if (!ids || !ids.length) return;
|
|
141
98
|
|
|
@@ -152,10 +109,9 @@ function callEzoic(ids) {
|
|
|
152
109
|
return false;
|
|
153
110
|
};
|
|
154
111
|
|
|
155
|
-
// Use cmd queue first (best if Ezoic loads later)
|
|
156
112
|
window.ezstandalone.cmd.push(function () { run(); });
|
|
157
113
|
|
|
158
|
-
//
|
|
114
|
+
// retry a few times (Ezoic can load late)
|
|
159
115
|
let tries = 0;
|
|
160
116
|
const maxTries = 6;
|
|
161
117
|
const timer = setInterval(function () {
|
|
@@ -164,7 +120,79 @@ function callEzoic(ids) {
|
|
|
164
120
|
}, 800);
|
|
165
121
|
}
|
|
166
122
|
|
|
123
|
+
function injectBetweenIncremental($items, pool, interval, wrapperClass) {
|
|
124
|
+
const total = $items.length;
|
|
125
|
+
const maxSlot = Math.floor(total / interval);
|
|
126
|
+
if (maxSlot <= 0) return [];
|
|
127
|
+
|
|
128
|
+
const newIds = [];
|
|
129
|
+
|
|
130
|
+
for (let slot = 1; slot <= maxSlot; slot++) {
|
|
131
|
+
if (injectedSlots.has(slot)) continue;
|
|
132
|
+
|
|
133
|
+
const index = slot * interval - 1;
|
|
134
|
+
const $target = $items.eq(index);
|
|
135
|
+
if (!$target.length) continue;
|
|
136
|
+
|
|
137
|
+
const id = pickNextId(pool);
|
|
138
|
+
if (!id) {
|
|
139
|
+
// pool exhausted: stop injecting further to avoid reusing ids and "jumping"
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const placeholder = '<div id="ezoic-pub-ad-placeholder-' + id + '"></div>';
|
|
144
|
+
const html = makeWrapperLike($target, wrapperClass, placeholder, 'data-ezoic-slot="' + slot + '" data-ezoic-id="' + id + '"');
|
|
145
|
+
|
|
146
|
+
$target.after(html);
|
|
147
|
+
|
|
148
|
+
injectedSlots.add(slot);
|
|
149
|
+
usedIds.add(id);
|
|
150
|
+
newIds.push(id);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return newIds;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function injectMessageIncremental($posts, pool, interval) {
|
|
157
|
+
const total = $posts.length;
|
|
158
|
+
const maxSlot = Math.floor(total / interval);
|
|
159
|
+
if (maxSlot <= 0) return [];
|
|
160
|
+
|
|
161
|
+
const newIds = [];
|
|
162
|
+
|
|
163
|
+
for (let slot = 1; slot <= maxSlot; slot++) {
|
|
164
|
+
if (injectedSlots.has(slot)) continue;
|
|
165
|
+
|
|
166
|
+
const index = slot * interval - 1;
|
|
167
|
+
const $target = $posts.eq(index);
|
|
168
|
+
if (!$target.length) continue;
|
|
169
|
+
|
|
170
|
+
const id = pickNextId(pool);
|
|
171
|
+
if (!id) break;
|
|
172
|
+
|
|
173
|
+
const inner = '<div class="content"><div id="ezoic-pub-ad-placeholder-' + id + '"></div></div>';
|
|
174
|
+
const html = makeWrapperLike($target, 'post ezoic-ad-post', inner, 'data-ezoic-slot="' + slot + '" data-ezoic-id="' + id + '"');
|
|
175
|
+
|
|
176
|
+
$target.after(html);
|
|
177
|
+
|
|
178
|
+
injectedSlots.add(slot);
|
|
179
|
+
usedIds.add(id);
|
|
180
|
+
newIds.push(id);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return newIds;
|
|
184
|
+
}
|
|
185
|
+
|
|
167
186
|
async function refreshAds() {
|
|
187
|
+
// reset state when navigating (ajaxify)
|
|
188
|
+
const key = currentPageKey();
|
|
189
|
+
if (pageKey !== key) {
|
|
190
|
+
pageKey = key;
|
|
191
|
+
resetPageState();
|
|
192
|
+
// also cleanup any injected wrappers that may have been left by browser bfcache
|
|
193
|
+
$('.ezoic-ad-post, .ezoic-ad-between, .ezoic-ad-topic').remove();
|
|
194
|
+
}
|
|
195
|
+
|
|
168
196
|
if (inFlight) { rerunRequested = true; return; }
|
|
169
197
|
inFlight = true;
|
|
170
198
|
|
|
@@ -186,45 +214,39 @@ async function refreshAds() {
|
|
|
186
214
|
|
|
187
215
|
if (!$posts.length && !$topicItems.length) return;
|
|
188
216
|
|
|
189
|
-
const
|
|
190
|
-
if (sig === lastSignature) return;
|
|
191
|
-
lastSignature = sig;
|
|
192
|
-
|
|
193
|
-
removeOurWrappers();
|
|
194
|
-
|
|
195
|
-
const activeIds = [];
|
|
217
|
+
const newIds = [];
|
|
196
218
|
|
|
197
219
|
// Your rule:
|
|
198
220
|
// - Category topic list: BETWEEN only
|
|
199
221
|
// - Topic page: MESSAGE only
|
|
200
222
|
if ($topicItems.length) {
|
|
201
223
|
if (cfg.enableBetweenAds && betweenPool.length) {
|
|
202
|
-
|
|
224
|
+
newIds.push(...injectBetweenIncremental($topicItems, betweenPool, betweenInterval, 'ezoic-ad-topic'));
|
|
203
225
|
}
|
|
204
|
-
callEzoic(
|
|
226
|
+
callEzoic(newIds);
|
|
205
227
|
return;
|
|
206
228
|
}
|
|
207
229
|
|
|
208
230
|
if ($posts.length) {
|
|
209
231
|
if (cfg.enableMessageAds && messagePool.length) {
|
|
210
|
-
|
|
232
|
+
newIds.push(...injectMessageIncremental($posts, messagePool, messageInterval));
|
|
211
233
|
}
|
|
212
|
-
callEzoic(
|
|
234
|
+
callEzoic(newIds);
|
|
213
235
|
}
|
|
214
236
|
} finally {
|
|
215
237
|
inFlight = false;
|
|
216
238
|
if (rerunRequested) {
|
|
217
239
|
rerunRequested = false;
|
|
218
|
-
setTimeout(refreshAds,
|
|
240
|
+
setTimeout(refreshAds, 120);
|
|
219
241
|
}
|
|
220
242
|
}
|
|
221
243
|
}
|
|
222
244
|
|
|
223
245
|
function debounceRefresh() {
|
|
224
246
|
clearTimeout(debounceTimer);
|
|
225
|
-
debounceTimer = setTimeout(refreshAds,
|
|
247
|
+
debounceTimer = setTimeout(refreshAds, 180);
|
|
226
248
|
}
|
|
227
249
|
|
|
228
250
|
$(document).ready(debounceRefresh);
|
|
229
251
|
$(window).on('action:ajaxify.end action:posts.loaded action:topic.loaded', debounceRefresh);
|
|
230
|
-
setTimeout(debounceRefresh,
|
|
252
|
+
setTimeout(debounceRefresh, 1800);
|