nodebb-plugin-calendar-onekite 12.0.2 → 12.0.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/lib/admin.js +33 -6
- package/lib/api.js +12 -3
- package/package.json +1 -1
- package/public/client.js +48 -21
package/lib/admin.js
CHANGED
|
@@ -202,13 +202,40 @@ admin.purgeByYear = async function (req, res) {
|
|
|
202
202
|
const startTs = new Date(Date.UTC(y, 0, 1)).getTime();
|
|
203
203
|
const endTs = new Date(Date.UTC(y + 1, 0, 1)).getTime() - 1;
|
|
204
204
|
|
|
205
|
-
const ids = await dbLayer.listReservationIdsByStartRange(startTs, endTs,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
205
|
+
const ids = await dbLayer.listReservationIdsByStartRange(startTs, endTs, 200000);
|
|
206
|
+
const ts = Date.now();
|
|
207
|
+
let removed = 0;
|
|
208
|
+
let keptForAccounting = 0;
|
|
209
|
+
|
|
210
|
+
// Bulk fetch to avoid one-by-one DB reads.
|
|
211
|
+
const chunkSize = 500;
|
|
212
|
+
for (let i = 0; i < ids.length; i += chunkSize) {
|
|
213
|
+
const chunk = ids.slice(i, i + chunkSize);
|
|
214
|
+
const objs = await dbLayer.getReservations(chunk);
|
|
215
|
+
for (let j = 0; j < chunk.length; j++) {
|
|
216
|
+
const rid = chunk[j];
|
|
217
|
+
const r = objs[j];
|
|
218
|
+
if (!r) {
|
|
219
|
+
// Ensure index doesn't keep dangling ids
|
|
220
|
+
try { await dbLayer.removeReservation(rid); } catch (e) {}
|
|
221
|
+
removed++;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// IMPORTANT: Do not purge accounting. If the reservation was paid,
|
|
226
|
+
// keep the object for exports and mark it as hidden from the calendar.
|
|
227
|
+
if (String(r.status) === 'paid' || r.paidAt) {
|
|
228
|
+
r.calendarPurgedAt = ts;
|
|
229
|
+
await dbLayer.saveReservation(r);
|
|
230
|
+
keptForAccounting++;
|
|
231
|
+
} else {
|
|
232
|
+
await dbLayer.removeReservation(rid);
|
|
233
|
+
removed++;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
210
236
|
}
|
|
211
|
-
|
|
237
|
+
|
|
238
|
+
res.json({ ok: true, removed, keptForAccounting });
|
|
212
239
|
};
|
|
213
240
|
|
|
214
241
|
admin.purgeSpecialEventsByYear = async function (req, res) {
|
package/lib/api.js
CHANGED
|
@@ -153,9 +153,17 @@ api.getEvents = async function (req, res) {
|
|
|
153
153
|
const wideStart = Math.max(0, startTs - 366 * 24 * 3600 * 1000);
|
|
154
154
|
const ids = await dbLayer.listReservationIdsByStartRange(wideStart, endTs, 5000);
|
|
155
155
|
const out = [];
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
|
|
157
|
+
// Bulk fetch to avoid one-by-one DB reads.
|
|
158
|
+
const chunkSize = 500;
|
|
159
|
+
for (let i = 0; i < ids.length; i += chunkSize) {
|
|
160
|
+
const chunk = ids.slice(i, i + chunkSize);
|
|
161
|
+
const objs = await dbLayer.getReservations(chunk);
|
|
162
|
+
for (let j = 0; j < chunk.length; j++) {
|
|
163
|
+
const r = objs[j];
|
|
164
|
+
if (!r) continue;
|
|
165
|
+
// Hidden by year purge (kept for accounting, not shown on calendar)
|
|
166
|
+
if (r.calendarPurgedAt) continue;
|
|
159
167
|
// Only show active statuses
|
|
160
168
|
if (!['pending', 'awaiting_payment', 'paid'].includes(r.status)) continue;
|
|
161
169
|
const rStart = parseInt(r.start, 10);
|
|
@@ -192,6 +200,7 @@ api.getEvents = async function (req, res) {
|
|
|
192
200
|
}
|
|
193
201
|
}
|
|
194
202
|
out.push(minimal);
|
|
203
|
+
}
|
|
195
204
|
}
|
|
196
205
|
}
|
|
197
206
|
|
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -36,6 +36,10 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
36
36
|
// interactions or quick re-renders.
|
|
37
37
|
let isDialogOpen = false;
|
|
38
38
|
|
|
39
|
+
// Used to avoid a visible "flash" of events when a live update triggers
|
|
40
|
+
// a refetch during the very first load.
|
|
41
|
+
let hasLoadedEventsOnce = false;
|
|
42
|
+
|
|
39
43
|
function escapeHtml(str) {
|
|
40
44
|
return String(str)
|
|
41
45
|
.replace(/&/g, '&')
|
|
@@ -887,34 +891,48 @@ function toDatetimeLocalValue(date) {
|
|
|
887
891
|
|
|
888
892
|
async function init(selector) {
|
|
889
893
|
|
|
890
|
-
const container = typeof selector === 'string' ? document.querySelector(selector) : selector;
|
|
891
|
-
if (!container) return;
|
|
892
|
-
|
|
893
|
-
// Prevent double-init (ajaxify + initial load, or return from payment)
|
|
894
|
-
if (container.dataset && container.dataset.onekiteCalendarInit === '1') {
|
|
895
|
-
// Still refresh the custom button label in case FC rerendered
|
|
896
|
-
try { refreshDesktopModeButton(); } catch (e) {}
|
|
897
|
-
return;
|
|
898
|
-
}
|
|
899
|
-
if (container.dataset) container.dataset.onekiteCalendarInit = '1';
|
|
894
|
+
const container = typeof selector === 'string' ? document.querySelector(selector) : selector;
|
|
895
|
+
if (!container) return;
|
|
900
896
|
|
|
901
|
-
//
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
897
|
+
// FullCalendar is loaded via <script> tags in the template. On ajaxify navigation,
|
|
898
|
+
// action:ajaxify.end can fire before those scripts finish downloading.
|
|
899
|
+
// IMPORTANT: do NOT mark the container as initialised until FullCalendar exists,
|
|
900
|
+
// otherwise we permanently block init and the calendar never loads.
|
|
901
|
+
if (typeof FullCalendar === 'undefined') {
|
|
902
|
+
// Retry for a short time.
|
|
903
|
+
const startedAt = Date.now();
|
|
904
|
+
const retry = () => {
|
|
905
|
+
if (typeof FullCalendar !== 'undefined') {
|
|
906
|
+
init(selector);
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
if (Date.now() - startedAt > 6000) {
|
|
910
|
+
showAlert('error', 'FullCalendar non chargé');
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
setTimeout(retry, 100);
|
|
914
|
+
};
|
|
915
|
+
setTimeout(retry, 0);
|
|
910
916
|
return;
|
|
911
917
|
}
|
|
912
918
|
|
|
913
|
-
|
|
914
|
-
|
|
919
|
+
// Prevent double-init (ajaxify + initial load, or return from payment)
|
|
920
|
+
if (container.dataset && container.dataset.onekiteCalendarInit === '1') {
|
|
921
|
+
try { refreshDesktopModeButton(); } catch (e) {}
|
|
915
922
|
return;
|
|
916
923
|
}
|
|
917
924
|
|
|
925
|
+
// If a previous instance exists (shouldn't, but happens in some navigation flows), destroy it.
|
|
926
|
+
try {
|
|
927
|
+
if (window.oneKiteCalendar && typeof window.oneKiteCalendar.destroy === 'function') {
|
|
928
|
+
window.oneKiteCalendar.destroy();
|
|
929
|
+
}
|
|
930
|
+
window.oneKiteCalendar = null;
|
|
931
|
+
} catch (e) {}
|
|
932
|
+
|
|
933
|
+
const el = typeof selector === 'string' ? document.querySelector(selector) : container;
|
|
934
|
+
if (!el) return;
|
|
935
|
+
|
|
918
936
|
const items = await loadItems();
|
|
919
937
|
const caps = await loadCapabilities().catch(() => ({}));
|
|
920
938
|
const canCreateSpecial = !!caps.canCreateSpecial;
|
|
@@ -1159,6 +1177,9 @@ try {
|
|
|
1159
1177
|
});
|
|
1160
1178
|
|
|
1161
1179
|
successCallback(mapped);
|
|
1180
|
+
// Mark first successful events load. We use this to prevent a
|
|
1181
|
+
// refetch-on-socket-update from flashing the UI during initial load.
|
|
1182
|
+
hasLoadedEventsOnce = true;
|
|
1162
1183
|
} catch (e) {
|
|
1163
1184
|
failureCallback(e);
|
|
1164
1185
|
}
|
|
@@ -1689,6 +1710,9 @@ try {
|
|
|
1689
1710
|
// Expose for live updates
|
|
1690
1711
|
try { window.oneKiteCalendar = calendar; } catch (e) {}
|
|
1691
1712
|
|
|
1713
|
+
// Mark initialised only after we have a valid FullCalendar instance.
|
|
1714
|
+
try { if (container.dataset) container.dataset.onekiteCalendarInit = '1'; } catch (e) {}
|
|
1715
|
+
|
|
1692
1716
|
calendar.render();
|
|
1693
1717
|
|
|
1694
1718
|
// Keep the custom button label stable even if FullCalendar rerenders the toolbar
|
|
@@ -1824,6 +1848,9 @@ try {
|
|
|
1824
1848
|
socket.on('event:calendar-onekite.reservationUpdated', function () {
|
|
1825
1849
|
try {
|
|
1826
1850
|
const cal = window.oneKiteCalendar;
|
|
1851
|
+
// Avoid a visible "flash" on first load (e.g. returning from payment
|
|
1852
|
+
// while a live update is emitted right away).
|
|
1853
|
+
if (!hasLoadedEventsOnce) return;
|
|
1827
1854
|
scheduleRefetch(cal);
|
|
1828
1855
|
} catch (e) {}
|
|
1829
1856
|
});
|