nodebb-plugin-calendar-onekite 11.1.96 → 11.1.98
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 +17 -15
- package/lib/api.js +1 -1
- package/package.json +1 -1
- package/public/client.js +120 -17
package/lib/admin.js
CHANGED
|
@@ -20,30 +20,32 @@ function formatFR(tsOrIso) {
|
|
|
20
20
|
|
|
21
21
|
async function sendEmail(template, toEmail, subject, data) {
|
|
22
22
|
if (!toEmail) return;
|
|
23
|
+
|
|
24
|
+
const settings = await meta.settings.get('calendar-onekite').catch(() => ({}));
|
|
25
|
+
const lang = (settings && settings.defaultLang) || (meta && meta.config && meta.config.defaultLang) || 'fr';
|
|
26
|
+
const params = Object.assign({}, data || {}, subject ? { subject } : {});
|
|
27
|
+
|
|
23
28
|
try {
|
|
24
29
|
if (typeof emailer.sendToEmail === 'function') {
|
|
30
|
+
// NodeBB: sendToEmail(template, email, language, params)
|
|
25
31
|
if (emailer.sendToEmail.length >= 4) {
|
|
26
|
-
await emailer.sendToEmail(template, toEmail,
|
|
32
|
+
await emailer.sendToEmail(template, toEmail, lang, params);
|
|
27
33
|
} else {
|
|
28
|
-
|
|
29
|
-
await emailer.sendToEmail(template, toEmail,
|
|
34
|
+
// Older signature: sendToEmail(template, email, params)
|
|
35
|
+
await emailer.sendToEmail(template, toEmail, params);
|
|
30
36
|
}
|
|
31
37
|
return;
|
|
32
38
|
}
|
|
33
39
|
if (typeof emailer.send === 'function') {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
if (emailer.send.length === 3) {
|
|
39
|
-
const dataWithSubject = Object.assign({}, data || {}, subject ? { subject } : {});
|
|
40
|
-
await emailer.send(template, toEmail, dataWithSubject);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
await emailer.send(template, toEmail, subject, data);
|
|
40
|
+
// Fallback: send(template, uid, params) - not usable without uid
|
|
41
|
+
return;
|
|
44
42
|
}
|
|
45
43
|
} catch (err) {
|
|
46
|
-
console.warn('[calendar-onekite] Failed to send email', {
|
|
44
|
+
console.warn('[calendar-onekite] Failed to send email', {
|
|
45
|
+
template,
|
|
46
|
+
toEmail,
|
|
47
|
+
err: err && err.message ? err.message : String(err),
|
|
48
|
+
});
|
|
47
49
|
}
|
|
48
50
|
}
|
|
49
51
|
|
|
@@ -194,7 +196,7 @@ admin.approveReservation = async function (req, res) {
|
|
|
194
196
|
pickupLon: r.pickupLon || '',
|
|
195
197
|
mapUrl,
|
|
196
198
|
validatedBy: r.approvedByUsername || '',
|
|
197
|
-
validatedByUrl: (r.approvedByUsername ? `https://www.onekite.com/
|
|
199
|
+
validatedByUrl: (r.approvedByUsername ? `https://www.onekite.com/user/${encodeURIComponent(String(r.approvedByUsername))}` : ''),
|
|
198
200
|
});
|
|
199
201
|
}
|
|
200
202
|
} catch (e) {}
|
package/lib/api.js
CHANGED
|
@@ -624,7 +624,7 @@ api.approveReservation = async function (req, res) {
|
|
|
624
624
|
mapUrl,
|
|
625
625
|
paymentUrl: r.paymentUrl || '',
|
|
626
626
|
validatedBy: r.approvedByUsername || '',
|
|
627
|
-
validatedByUrl: (r.approvedByUsername ? `https://www.onekite.com/
|
|
627
|
+
validatedByUrl: (r.approvedByUsername ? `https://www.onekite.com/user/${encodeURIComponent(String(r.approvedByUsername))}` : ''),
|
|
628
628
|
});
|
|
629
629
|
}
|
|
630
630
|
return res.json({ ok: true });
|
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -188,6 +188,20 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
188
188
|
const lon = parseFloat(a.getAttribute('data-lon'));
|
|
189
189
|
openMapViewer('Adresse', addr, lat, lon);
|
|
190
190
|
});
|
|
191
|
+
// Close any open Bootbox modal when navigating to a user profile from within a modal
|
|
192
|
+
document.addEventListener('click', (ev) => {
|
|
193
|
+
const a = ev.target && ev.target.closest ? ev.target.closest('a.onekite-user-link') : null;
|
|
194
|
+
if (!a) return;
|
|
195
|
+
const inModal = !!(a.closest && a.closest('.modal, .bootbox'));
|
|
196
|
+
if (!inModal) return;
|
|
197
|
+
const href = a.getAttribute('href');
|
|
198
|
+
if (!href) return;
|
|
199
|
+
ev.preventDefault();
|
|
200
|
+
try { bootbox.hideAll(); } catch (e) {}
|
|
201
|
+
setTimeout(() => { window.location.href = href; }, 50);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
|
|
191
205
|
|
|
192
206
|
function statusLabel(s) {
|
|
193
207
|
const map = {
|
|
@@ -228,7 +242,12 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
228
242
|
...opts,
|
|
229
243
|
});
|
|
230
244
|
if (!res.ok) {
|
|
231
|
-
|
|
245
|
+
let payload = null;
|
|
246
|
+
try { payload = await res.json(); } catch (e) {}
|
|
247
|
+
const err = new Error(`${res.status}`);
|
|
248
|
+
err.status = res.status;
|
|
249
|
+
err.payload = payload;
|
|
250
|
+
throw err;
|
|
232
251
|
}
|
|
233
252
|
return await res.json();
|
|
234
253
|
}
|
|
@@ -634,7 +653,31 @@ function toDatetimeLocalValue(date) {
|
|
|
634
653
|
const canCreateSpecial = !!caps.canCreateSpecial;
|
|
635
654
|
const canDeleteSpecial = !!caps.canDeleteSpecial;
|
|
636
655
|
|
|
637
|
-
|
|
656
|
+
// Current creation mode: reservation (location) or special (event)
|
|
657
|
+
let mode = 'reservation';
|
|
658
|
+
function refreshDesktopModeButton() {
|
|
659
|
+
try {
|
|
660
|
+
const btn = document.querySelector('#onekite-calendar .fc-newSpecial-button');
|
|
661
|
+
if (!btn) return;
|
|
662
|
+
const isSpecial = mode === 'special';
|
|
663
|
+
btn.textContent = isSpecial ? 'Évènement ✓' : 'Évènement';
|
|
664
|
+
btn.classList.toggle('onekite-active', isSpecial);
|
|
665
|
+
} catch (e) {}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function setMode(next) {
|
|
669
|
+
mode = next;
|
|
670
|
+
refreshDesktopModeButton();
|
|
671
|
+
try {
|
|
672
|
+
const mb = document.querySelector('#onekite-mobile-controls .onekite-mode-btn');
|
|
673
|
+
if (mb) {
|
|
674
|
+
const isSpecial = mode === 'special';
|
|
675
|
+
mb.textContent = isSpecial ? 'Mode évènement ✓' : 'Mode évènement';
|
|
676
|
+
mb.classList.toggle('onekite-active', isSpecial);
|
|
677
|
+
}
|
|
678
|
+
} catch (e) {}
|
|
679
|
+
}
|
|
680
|
+
// or 'special'
|
|
638
681
|
|
|
639
682
|
// Inject lightweight responsive CSS once.
|
|
640
683
|
try {
|
|
@@ -660,7 +703,21 @@ function toDatetimeLocalValue(date) {
|
|
|
660
703
|
#onekite-calendar .fc .fc-toolbar-title { font-size: 1rem; }
|
|
661
704
|
#onekite-calendar .fc .fc-button { padding: .2rem .4rem; font-size: .8rem; }
|
|
662
705
|
}
|
|
663
|
-
|
|
706
|
+
|
|
707
|
+
/* Violet action button (events mode) */
|
|
708
|
+
#onekite-calendar .fc .fc-newSpecial-button,
|
|
709
|
+
.onekite-btn-violet {
|
|
710
|
+
background: #6f42c1 !important;
|
|
711
|
+
border-color: #6f42c1 !important;
|
|
712
|
+
color: #fff !important;
|
|
713
|
+
}
|
|
714
|
+
/* Active state */
|
|
715
|
+
#onekite-calendar .fc .fc-newSpecial-button.onekite-active,
|
|
716
|
+
.onekite-btn-violet.onekite-active {
|
|
717
|
+
filter: brightness(0.95);
|
|
718
|
+
box-shadow: 0 0 0 0.15rem rgba(111,66,193,.25);
|
|
719
|
+
}
|
|
720
|
+
`;
|
|
664
721
|
document.head.appendChild(st);
|
|
665
722
|
}
|
|
666
723
|
} catch (e) {}
|
|
@@ -704,8 +761,10 @@ function toDatetimeLocalValue(date) {
|
|
|
704
761
|
newSpecial: {
|
|
705
762
|
text: 'Évènement',
|
|
706
763
|
click: () => {
|
|
707
|
-
mode
|
|
708
|
-
|
|
764
|
+
setMode((mode === 'special') ? 'reservation' : 'special');
|
|
765
|
+
if (mode === 'special') {
|
|
766
|
+
showAlert('success', 'Mode évènement : sélectionne une plage (date/heure) sur le calendrier.');
|
|
767
|
+
}
|
|
709
768
|
},
|
|
710
769
|
},
|
|
711
770
|
} : {},
|
|
@@ -730,7 +789,7 @@ function toDatetimeLocalValue(date) {
|
|
|
730
789
|
try {
|
|
731
790
|
if (mode === 'special' && canCreateSpecial) {
|
|
732
791
|
const payload = await openSpecialEventDialog(info);
|
|
733
|
-
|
|
792
|
+
setMode('reservation');
|
|
734
793
|
if (!payload) {
|
|
735
794
|
calendar.unselect();
|
|
736
795
|
isDialogOpen = false;
|
|
@@ -779,13 +838,22 @@ function toDatetimeLocalValue(date) {
|
|
|
779
838
|
calendar.unselect();
|
|
780
839
|
isDialogOpen = false;
|
|
781
840
|
} catch (e) {
|
|
782
|
-
const code = String(e && e.message || '');
|
|
841
|
+
const code = String((e && (e.status || e.message)) || '');
|
|
842
|
+
const payload = e && e.payload ? e.payload : null;
|
|
843
|
+
|
|
783
844
|
if (code === '403') {
|
|
784
|
-
|
|
845
|
+
const msg = payload && (payload.message || payload.error || payload.msg) ? String(payload.message || payload.error || payload.msg) : '';
|
|
846
|
+
const c = payload && payload.code ? String(payload.code) : '';
|
|
847
|
+
if (c === 'NOT_MEMBER' || /adh(é|e)rent/i.test(msg) || /membership/i.test(msg)) {
|
|
848
|
+
showAlert('error', msg || 'Vous devez être adhérent pour pouvoir effectuer une réservation.');
|
|
849
|
+
} else {
|
|
850
|
+
showAlert('error', msg || 'Impossible de créer la demande : droits insuffisants (groupe).');
|
|
851
|
+
}
|
|
785
852
|
} else if (code === '409') {
|
|
786
853
|
showAlert('error', 'Impossible : au moins un matériel est déjà réservé ou en attente sur cette période.');
|
|
787
854
|
} else {
|
|
788
|
-
|
|
855
|
+
const msg = payload && (payload.message || payload.error || payload.msg) ? String(payload.message || payload.error || payload.msg) : '';
|
|
856
|
+
showAlert('error', msg || 'Erreur lors de la création de la demande.');
|
|
789
857
|
}
|
|
790
858
|
calendar.unselect();
|
|
791
859
|
isDialogOpen = false;
|
|
@@ -794,6 +862,39 @@ function toDatetimeLocalValue(date) {
|
|
|
794
862
|
dateClick: async function (info) {
|
|
795
863
|
// One-day selection convenience
|
|
796
864
|
const start = info.date;
|
|
865
|
+
|
|
866
|
+
// In "special event" mode, a simple click should propose a one-day event (not two days in the modal)
|
|
867
|
+
if (mode === 'special' && canCreateSpecial) {
|
|
868
|
+
if (isDialogOpen) {
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
isDialogOpen = true;
|
|
872
|
+
try {
|
|
873
|
+
// Default to a same-day event: 00:00 -> 23:59
|
|
874
|
+
const end = new Date(start.getTime() + (23 * 60 + 59) * 60 * 1000);
|
|
875
|
+
const payload = await openSpecialEventDialog({ start, end, allDay: true });
|
|
876
|
+
setMode('reservation');
|
|
877
|
+
if (!payload) {
|
|
878
|
+
isDialogOpen = false;
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
await fetchJson('/api/v3/plugins/calendar-onekite/special-events', {
|
|
882
|
+
method: 'POST',
|
|
883
|
+
body: JSON.stringify(payload),
|
|
884
|
+
}).catch(async () => {
|
|
885
|
+
return await fetchJson('/api/plugins/calendar-onekite/special-events', {
|
|
886
|
+
method: 'POST',
|
|
887
|
+
body: JSON.stringify(payload),
|
|
888
|
+
});
|
|
889
|
+
});
|
|
890
|
+
showAlert('success', 'Évènement créé.');
|
|
891
|
+
calendar.refetchEvents();
|
|
892
|
+
} finally {
|
|
893
|
+
isDialogOpen = false;
|
|
894
|
+
}
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
|
|
797
898
|
const end = new Date(start.getTime() + 24 * 60 * 60 * 1000);
|
|
798
899
|
calendar.select(start, end);
|
|
799
900
|
},
|
|
@@ -804,7 +905,7 @@ function toDatetimeLocalValue(date) {
|
|
|
804
905
|
if (p.type === 'special') {
|
|
805
906
|
const username = String(p.username || '').trim();
|
|
806
907
|
const userLine = username
|
|
807
|
-
? `<div class="mb-2"><strong>Créé par</strong><br><a href="${window.location.origin}/user/${encodeURIComponent(username)}">${escapeHtml(username)}</a></div>`
|
|
908
|
+
? `<div class="mb-2"><strong>Créé par</strong><br><a class="onekite-user-link" href="${window.location.origin}/user/${encodeURIComponent(username)}">${escapeHtml(username)}</a></div>`
|
|
808
909
|
: '';
|
|
809
910
|
const addr = String(p.pickupAddress || '').trim();
|
|
810
911
|
const lat = Number(p.pickupLat);
|
|
@@ -856,7 +957,7 @@ function toDatetimeLocalValue(date) {
|
|
|
856
957
|
// Reserved-by line (user profile link)
|
|
857
958
|
const username = String(p.username || p.user || p.reservedBy || p.ownerUsername || '').trim();
|
|
858
959
|
const userLine = username
|
|
859
|
-
? `<div class="mb-2"><strong>
|
|
960
|
+
? `<div class="mb-2"><strong>Réservée par</strong><br><a class="onekite-user-link" href="${window.location.origin}/user/${encodeURIComponent(username)}">${escapeHtml(username)}</a></div>`
|
|
860
961
|
: '';
|
|
861
962
|
const itemsHtml = (() => {
|
|
862
963
|
const names = Array.isArray(p.itemNames) ? p.itemNames : (typeof p.itemNames === 'string' && p.itemNames.trim() ? p.itemNames.split(',').map(s=>s.trim()).filter(Boolean) : (p.itemName ? [p.itemName] : []));
|
|
@@ -869,7 +970,7 @@ function toDatetimeLocalValue(date) {
|
|
|
869
970
|
|
|
870
971
|
const approvedBy = String(p.approvedByUsername || '').trim();
|
|
871
972
|
const validatedByHtml = approvedBy
|
|
872
|
-
? `<div class=\"mb-2\"><strong>Validée par</strong><br><a href=\"https://www.onekite.com/
|
|
973
|
+
? `<div class=\"mb-2\"><strong>Validée par</strong><br><a href=\"https://www.onekite.com/user/${encodeURIComponent(approvedBy)}\">${escapeHtml(approvedBy)}</a></div>`
|
|
873
974
|
: '';
|
|
874
975
|
|
|
875
976
|
// Pickup details (address / time / notes) shown once validated
|
|
@@ -1126,6 +1227,9 @@ function toDatetimeLocalValue(date) {
|
|
|
1126
1227
|
|
|
1127
1228
|
calendar.render();
|
|
1128
1229
|
|
|
1230
|
+
refreshDesktopModeButton();
|
|
1231
|
+
|
|
1232
|
+
|
|
1129
1233
|
// Mobile controls: view (month/week) + mode (reservation/event) without bloating the header.
|
|
1130
1234
|
try {
|
|
1131
1235
|
const controlsId = 'onekite-mobile-controls';
|
|
@@ -1173,17 +1277,16 @@ function toDatetimeLocalValue(date) {
|
|
|
1173
1277
|
if (canCreateSpecial) {
|
|
1174
1278
|
const modeBtn = document.createElement('button');
|
|
1175
1279
|
modeBtn.type = 'button';
|
|
1176
|
-
modeBtn.className = 'btn btn-sm btn-
|
|
1280
|
+
modeBtn.className = 'btn btn-sm onekite-btn-violet onekite-mode-btn';
|
|
1177
1281
|
function refreshModeBtn() {
|
|
1178
1282
|
const isSpecial = mode === 'special';
|
|
1179
1283
|
modeBtn.textContent = isSpecial ? 'Mode évènement ✓' : 'Mode évènement';
|
|
1180
1284
|
modeBtn.classList.toggle('active', isSpecial);
|
|
1181
|
-
//
|
|
1182
|
-
modeBtn.classList.toggle('
|
|
1183
|
-
modeBtn.classList.toggle('btn-outline-warning', !isSpecial);
|
|
1285
|
+
// Keep violet, only toggle active class
|
|
1286
|
+
modeBtn.classList.toggle('onekite-active', isSpecial);
|
|
1184
1287
|
}
|
|
1185
1288
|
modeBtn.addEventListener('click', () => {
|
|
1186
|
-
|
|
1289
|
+
setMode((mode === 'special') ? 'reservation' : 'special');
|
|
1187
1290
|
refreshModeBtn();
|
|
1188
1291
|
if (mode === 'special') {
|
|
1189
1292
|
showAlert('success', 'Mode évènement : sélectionne une plage (date/heure) sur le calendrier.');
|