nodebb-plugin-calendar-onekite 11.1.93 → 11.1.95
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 +8 -15
- package/package.json +1 -1
- package/public/client.js +63 -101
- package/templates/calendar-onekite.tpl +0 -25
package/lib/admin.js
CHANGED
|
@@ -20,39 +20,33 @@ function formatFR(tsOrIso) {
|
|
|
20
20
|
|
|
21
21
|
async function sendEmail(template, toEmail, subject, data) {
|
|
22
22
|
if (!toEmail) return;
|
|
23
|
-
const params = Object.assign({}, data || {});
|
|
24
|
-
if (subject) {
|
|
25
|
-
params.subject = subject;
|
|
26
|
-
}
|
|
27
|
-
// NodeBB emailer expects: (template, email, language, params)
|
|
28
|
-
const lang = params.lang || (meta && meta.config && (meta.config.defaultLang || meta.config.defaultLanguage)) || 'fr';
|
|
29
|
-
|
|
30
23
|
try {
|
|
31
24
|
if (typeof emailer.sendToEmail === 'function') {
|
|
32
25
|
if (emailer.sendToEmail.length >= 4) {
|
|
33
|
-
await emailer.sendToEmail(template, toEmail,
|
|
26
|
+
await emailer.sendToEmail(template, toEmail, subject, data);
|
|
34
27
|
} else {
|
|
35
|
-
|
|
28
|
+
const dataWithSubject = Object.assign({}, data || {}, subject ? { subject } : {});
|
|
29
|
+
await emailer.sendToEmail(template, toEmail, dataWithSubject);
|
|
36
30
|
}
|
|
37
31
|
return;
|
|
38
32
|
}
|
|
39
33
|
if (typeof emailer.send === 'function') {
|
|
40
34
|
if (emailer.send.length >= 4) {
|
|
41
|
-
await emailer.send(template, toEmail,
|
|
35
|
+
await emailer.send(template, toEmail, subject, data);
|
|
42
36
|
return;
|
|
43
37
|
}
|
|
44
38
|
if (emailer.send.length === 3) {
|
|
45
|
-
|
|
39
|
+
const dataWithSubject = Object.assign({}, data || {}, subject ? { subject } : {});
|
|
40
|
+
await emailer.send(template, toEmail, dataWithSubject);
|
|
46
41
|
return;
|
|
47
42
|
}
|
|
48
|
-
await emailer.send(template, toEmail,
|
|
43
|
+
await emailer.send(template, toEmail, subject, data);
|
|
49
44
|
}
|
|
50
45
|
} catch (err) {
|
|
51
|
-
console.warn('[calendar-onekite] Failed to send email', { template, toEmail, err: String(
|
|
46
|
+
console.warn('[calendar-onekite] Failed to send email', { template, toEmail, err: String(err && err.message || err) });
|
|
52
47
|
}
|
|
53
48
|
}
|
|
54
49
|
|
|
55
|
-
|
|
56
50
|
function normalizeCallbackUrl(configured, meta) {
|
|
57
51
|
const base = (meta && meta.config && (meta.config.url || meta.config['url'])) ? (meta.config.url || meta.config['url']) : '';
|
|
58
52
|
let url = (configured || '').trim();
|
|
@@ -523,4 +517,3 @@ admin.purgeAccounting = async function (req, res) {
|
|
|
523
517
|
|
|
524
518
|
|
|
525
519
|
module.exports = admin;
|
|
526
|
-
|
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -58,7 +58,7 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
58
58
|
cancel: {
|
|
59
59
|
label: 'Annuler',
|
|
60
60
|
className: 'btn-secondary',
|
|
61
|
-
callback: () =>
|
|
61
|
+
callback: () => resolve(null),
|
|
62
62
|
},
|
|
63
63
|
ok: {
|
|
64
64
|
label: 'Créer',
|
|
@@ -71,7 +71,7 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
71
71
|
const notes = (document.getElementById('onekite-se-notes')?.value || '').trim();
|
|
72
72
|
const lat = (document.getElementById('onekite-se-lat')?.value || '').trim();
|
|
73
73
|
const lon = (document.getElementById('onekite-se-lon')?.value || '').trim();
|
|
74
|
-
|
|
74
|
+
resolve({ title, start: startVal, end: endVal, address, notes, lat, lon });
|
|
75
75
|
return true;
|
|
76
76
|
},
|
|
77
77
|
},
|
|
@@ -497,26 +497,6 @@ function attachAddressAutocomplete(inputEl, onPick) {
|
|
|
497
497
|
}
|
|
498
498
|
|
|
499
499
|
|
|
500
|
-
function formatPeriodWithTime(start, end) {
|
|
501
|
-
const s = formatDtWithTime(start);
|
|
502
|
-
if (!end) {
|
|
503
|
-
return s;
|
|
504
|
-
}
|
|
505
|
-
try {
|
|
506
|
-
const st = new Date(start).getTime();
|
|
507
|
-
const en = new Date(end).getTime();
|
|
508
|
-
if (Number.isFinite(st) && Number.isFinite(en) && en <= st) {
|
|
509
|
-
return s;
|
|
510
|
-
}
|
|
511
|
-
} catch (e) {}
|
|
512
|
-
const eTxt = formatDtWithTime(end);
|
|
513
|
-
if (!eTxt || eTxt === 'Invalid Date') {
|
|
514
|
-
return s;
|
|
515
|
-
}
|
|
516
|
-
return `${s} → ${eTxt}`;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
|
|
520
500
|
|
|
521
501
|
function toLocalYmd(date) {
|
|
522
502
|
const d = new Date(date);
|
|
@@ -591,8 +571,6 @@ function toDatetimeLocalValue(date) {
|
|
|
591
571
|
`;
|
|
592
572
|
|
|
593
573
|
return new Promise((resolve) => {
|
|
594
|
-
let __settled = false;
|
|
595
|
-
const __settle = (v) => { if (__settled) return; __settled = true; __settle(v); };
|
|
596
574
|
const dlg = bootbox.dialog({
|
|
597
575
|
title: 'Demander une réservation',
|
|
598
576
|
message: messageHtml,
|
|
@@ -601,7 +579,7 @@ function toDatetimeLocalValue(date) {
|
|
|
601
579
|
label: 'Annuler',
|
|
602
580
|
className: 'btn-secondary',
|
|
603
581
|
callback: function () {
|
|
604
|
-
|
|
582
|
+
resolve(null);
|
|
605
583
|
},
|
|
606
584
|
},
|
|
607
585
|
ok: {
|
|
@@ -617,16 +595,13 @@ function toDatetimeLocalValue(date) {
|
|
|
617
595
|
const itemNames = cbs.map(cb => cb.getAttribute('data-name'));
|
|
618
596
|
const sum = cbs.reduce((acc, cb) => acc + (parseFloat(cb.getAttribute('data-price') || '0') || 0), 0);
|
|
619
597
|
const total = (sum / 100) * days;
|
|
620
|
-
|
|
598
|
+
resolve({ itemIds, itemNames, total, days });
|
|
621
599
|
},
|
|
622
600
|
},
|
|
623
601
|
},
|
|
624
602
|
});
|
|
625
603
|
|
|
626
|
-
|
|
627
|
-
// Ensure the promise resolves even if the modal is dismissed externally (e.g. mobile rotate)
|
|
628
|
-
dlg.on('hidden.bs.modal', () => __settle(null));
|
|
629
|
-
// live total update
|
|
604
|
+
// live total update
|
|
630
605
|
setTimeout(() => {
|
|
631
606
|
const totalEl = document.getElementById('onekite-total');
|
|
632
607
|
function refreshTotal() {
|
|
@@ -659,7 +634,30 @@ function toDatetimeLocalValue(date) {
|
|
|
659
634
|
const canCreateSpecial = !!caps.canCreateSpecial;
|
|
660
635
|
const canDeleteSpecial = !!caps.canDeleteSpecial;
|
|
661
636
|
|
|
662
|
-
let
|
|
637
|
+
let setMode('reservation');
|
|
638
|
+
function refreshDesktopModeButton() {
|
|
639
|
+
try {
|
|
640
|
+
const btn = document.querySelector('#onekite-calendar .fc-newSpecial-button');
|
|
641
|
+
if (!btn) return;
|
|
642
|
+
const isSpecial = mode === 'special';
|
|
643
|
+
btn.textContent = isSpecial ? 'Évènement ✓' : 'Évènement';
|
|
644
|
+
btn.classList.toggle('onekite-active', isSpecial);
|
|
645
|
+
} catch (e) {}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function setMode(next) {
|
|
649
|
+
mode = next;
|
|
650
|
+
refreshDesktopModeButton();
|
|
651
|
+
try {
|
|
652
|
+
const mb = document.querySelector('#onekite-mobile-controls .onekite-mode-btn');
|
|
653
|
+
if (mb) {
|
|
654
|
+
const isSpecial = mode === 'special';
|
|
655
|
+
mb.textContent = isSpecial ? 'Mode évènement ✓' : 'Mode évènement';
|
|
656
|
+
mb.classList.toggle('onekite-active', isSpecial);
|
|
657
|
+
}
|
|
658
|
+
} catch (e) {}
|
|
659
|
+
}
|
|
660
|
+
// or 'special'
|
|
663
661
|
|
|
664
662
|
// Inject lightweight responsive CSS once.
|
|
665
663
|
try {
|
|
@@ -685,7 +683,21 @@ function toDatetimeLocalValue(date) {
|
|
|
685
683
|
#onekite-calendar .fc .fc-toolbar-title { font-size: 1rem; }
|
|
686
684
|
#onekite-calendar .fc .fc-button { padding: .2rem .4rem; font-size: .8rem; }
|
|
687
685
|
}
|
|
688
|
-
|
|
686
|
+
|
|
687
|
+
/* Violet action button (events mode) */
|
|
688
|
+
#onekite-calendar .fc .fc-newSpecial-button,
|
|
689
|
+
.onekite-btn-violet {
|
|
690
|
+
background: #6f42c1 !important;
|
|
691
|
+
border-color: #6f42c1 !important;
|
|
692
|
+
color: #fff !important;
|
|
693
|
+
}
|
|
694
|
+
/* Active state */
|
|
695
|
+
#onekite-calendar .fc .fc-newSpecial-button.onekite-active,
|
|
696
|
+
.onekite-btn-violet.onekite-active {
|
|
697
|
+
filter: brightness(0.95);
|
|
698
|
+
box-shadow: 0 0 0 0.15rem rgba(111,66,193,.25);
|
|
699
|
+
}
|
|
700
|
+
`;
|
|
689
701
|
document.head.appendChild(st);
|
|
690
702
|
}
|
|
691
703
|
} catch (e) {}
|
|
@@ -715,31 +727,6 @@ function toDatetimeLocalValue(date) {
|
|
|
715
727
|
right: (canCreateSpecial ? 'newSpecial ' : '') + 'dayGridMonth,timeGridWeek',
|
|
716
728
|
};
|
|
717
729
|
|
|
718
|
-
// Mode UI helpers (desktop FullCalendar button + mobile toggle)
|
|
719
|
-
let mobileModeBtn = null;
|
|
720
|
-
function refreshModeIndicators() {
|
|
721
|
-
const isSpecial = mode === 'special';
|
|
722
|
-
|
|
723
|
-
// Desktop "Évènement" button in the FullCalendar header
|
|
724
|
-
try {
|
|
725
|
-
const desktopBtn = document.querySelector('#onekite-calendar .fc-newSpecial-button');
|
|
726
|
-
if (desktopBtn) {
|
|
727
|
-
desktopBtn.textContent = isSpecial ? 'Évènement ✓' : 'Évènement';
|
|
728
|
-
desktopBtn.classList.toggle('onekite-mode-active', isSpecial);
|
|
729
|
-
}
|
|
730
|
-
} catch (e) {}
|
|
731
|
-
|
|
732
|
-
// Mobile mode toggle (under the calendar)
|
|
733
|
-
try {
|
|
734
|
-
if (mobileModeBtn) {
|
|
735
|
-
mobileModeBtn.textContent = isSpecial ? 'Mode évènement ✓' : 'Mode évènement';
|
|
736
|
-
mobileModeBtn.classList.toggle('active', isSpecial);
|
|
737
|
-
mobileModeBtn.classList.toggle('onekite-btn-violet', isSpecial);
|
|
738
|
-
mobileModeBtn.classList.toggle('onekite-btn-violet-outline', !isSpecial);
|
|
739
|
-
}
|
|
740
|
-
} catch (e) {}
|
|
741
|
-
}
|
|
742
|
-
|
|
743
730
|
const calendar = new FullCalendar.Calendar(el, {
|
|
744
731
|
initialView: 'dayGridMonth',
|
|
745
732
|
height: 'auto',
|
|
@@ -754,8 +741,7 @@ function toDatetimeLocalValue(date) {
|
|
|
754
741
|
newSpecial: {
|
|
755
742
|
text: 'Évènement',
|
|
756
743
|
click: () => {
|
|
757
|
-
|
|
758
|
-
refreshModeIndicators();
|
|
744
|
+
setMode((mode === 'special') ? 'reservation' : 'special');
|
|
759
745
|
if (mode === 'special') {
|
|
760
746
|
showAlert('success', 'Mode évènement : sélectionne une plage (date/heure) sur le calendrier.');
|
|
761
747
|
}
|
|
@@ -783,7 +769,7 @@ function toDatetimeLocalValue(date) {
|
|
|
783
769
|
try {
|
|
784
770
|
if (mode === 'special' && canCreateSpecial) {
|
|
785
771
|
const payload = await openSpecialEventDialog(info);
|
|
786
|
-
|
|
772
|
+
setMode('reservation');
|
|
787
773
|
if (!payload) {
|
|
788
774
|
calendar.unselect();
|
|
789
775
|
isDialogOpen = false;
|
|
@@ -845,35 +831,8 @@ function toDatetimeLocalValue(date) {
|
|
|
845
831
|
}
|
|
846
832
|
},
|
|
847
833
|
dateClick: async function (info) {
|
|
848
|
-
// One-day selection convenience
|
|
849
|
-
// For reservations, selecting a day makes a 1-day range.
|
|
850
|
-
// For special events ("mode évènement"), we want a single date (no implicit J+1 end).
|
|
834
|
+
// One-day selection convenience
|
|
851
835
|
const start = info.date;
|
|
852
|
-
|
|
853
|
-
if (mode === 'special' && canCreateSpecial) {
|
|
854
|
-
if (isDialogOpen) {
|
|
855
|
-
return;
|
|
856
|
-
}
|
|
857
|
-
isDialogOpen = true;
|
|
858
|
-
try {
|
|
859
|
-
const payload = await openSpecialEventDialog({ start, end: start });
|
|
860
|
-
mode = 'reservation';
|
|
861
|
-
if (!payload) {
|
|
862
|
-
isDialogOpen = false;
|
|
863
|
-
return;
|
|
864
|
-
}
|
|
865
|
-
await createSpecialEvent(payload);
|
|
866
|
-
showAlert('success', 'Évènement créé.');
|
|
867
|
-
calendar.refetchEvents();
|
|
868
|
-
isDialogOpen = false;
|
|
869
|
-
return;
|
|
870
|
-
} catch (e) {
|
|
871
|
-
showAlert('error', 'Impossible de créer l\'évènement.');
|
|
872
|
-
isDialogOpen = false;
|
|
873
|
-
return;
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
|
|
877
836
|
const end = new Date(start.getTime() + 24 * 60 * 60 * 1000);
|
|
878
837
|
calendar.select(start, end);
|
|
879
838
|
},
|
|
@@ -899,7 +858,7 @@ function toDatetimeLocalValue(date) {
|
|
|
899
858
|
const html = `
|
|
900
859
|
<div class="mb-2"><strong>Titre</strong><br>${escapeHtml(p.title || ev.title || '')}</div>
|
|
901
860
|
${userLine}
|
|
902
|
-
<div class="mb-2"><strong>Période</strong><br>${escapeHtml(
|
|
861
|
+
<div class="mb-2"><strong>Période</strong><br>${escapeHtml(formatDtWithTime(ev.start))} → ${escapeHtml(formatDtWithTime(ev.end))}</div>
|
|
903
862
|
${addr ? `<div class="mb-2"><strong>Adresse</strong><br>${addrHtml}</div>` : ''}
|
|
904
863
|
${notes ? `<div class="mb-2"><strong>Notes</strong><br>${escapeHtml(notes).replace(/\n/g,'<br>')}</div>` : ''}
|
|
905
864
|
`;
|
|
@@ -1104,10 +1063,7 @@ function toDatetimeLocalValue(date) {
|
|
|
1104
1063
|
},
|
|
1105
1064
|
});
|
|
1106
1065
|
|
|
1107
|
-
|
|
1108
|
-
// Ensure the promise resolves even if the modal is dismissed externally (e.g. mobile rotate)
|
|
1109
|
-
dlg.on('hidden.bs.modal', () => __settle(null));
|
|
1110
|
-
// Init Leaflet map once the modal is visible.
|
|
1066
|
+
// Init Leaflet map once the modal is visible.
|
|
1111
1067
|
dlg.on('shown.bs.modal', async () => {
|
|
1112
1068
|
try {
|
|
1113
1069
|
const L = await loadLeaflet();
|
|
@@ -1208,8 +1164,9 @@ function toDatetimeLocalValue(date) {
|
|
|
1208
1164
|
try { window.oneKiteCalendar = calendar; } catch (e) {}
|
|
1209
1165
|
|
|
1210
1166
|
calendar.render();
|
|
1211
|
-
|
|
1212
|
-
|
|
1167
|
+
|
|
1168
|
+
refreshDesktopModeButton();
|
|
1169
|
+
|
|
1213
1170
|
|
|
1214
1171
|
// Mobile controls: view (month/week) + mode (reservation/event) without bloating the header.
|
|
1215
1172
|
try {
|
|
@@ -1258,17 +1215,22 @@ function toDatetimeLocalValue(date) {
|
|
|
1258
1215
|
if (canCreateSpecial) {
|
|
1259
1216
|
const modeBtn = document.createElement('button');
|
|
1260
1217
|
modeBtn.type = 'button';
|
|
1261
|
-
modeBtn.className = 'btn btn-sm onekite-btn-violet
|
|
1262
|
-
|
|
1263
|
-
|
|
1218
|
+
modeBtn.className = 'btn btn-sm onekite-btn-violet onekite-mode-btn';
|
|
1219
|
+
function refreshModeBtn() {
|
|
1220
|
+
const isSpecial = mode === 'special';
|
|
1221
|
+
modeBtn.textContent = isSpecial ? 'Mode évènement ✓' : 'Mode évènement';
|
|
1222
|
+
modeBtn.classList.toggle('active', isSpecial);
|
|
1223
|
+
// Keep violet, only toggle active class
|
|
1224
|
+
modeBtn.classList.toggle('onekite-active', isSpecial);
|
|
1225
|
+
}
|
|
1264
1226
|
modeBtn.addEventListener('click', () => {
|
|
1265
|
-
|
|
1266
|
-
|
|
1227
|
+
setMode((mode === 'special') ? 'reservation' : 'special');
|
|
1228
|
+
refreshModeBtn();
|
|
1267
1229
|
if (mode === 'special') {
|
|
1268
1230
|
showAlert('success', 'Mode évènement : sélectionne une plage (date/heure) sur le calendrier.');
|
|
1269
1231
|
}
|
|
1270
1232
|
});
|
|
1271
|
-
|
|
1233
|
+
refreshModeBtn();
|
|
1272
1234
|
controls.appendChild(modeBtn);
|
|
1273
1235
|
}
|
|
1274
1236
|
|
|
@@ -27,31 +27,6 @@
|
|
|
27
27
|
.fc .fc-newSpecial-button:hover {
|
|
28
28
|
filter: brightness(0.95);
|
|
29
29
|
}
|
|
30
|
-
/* Indicate active "mode évènement" (desktop) */
|
|
31
|
-
.fc .fc-newSpecial-button.onekite-mode-active {
|
|
32
|
-
box-shadow: inset 0 0 0 2px rgba(255,255,255,0.35);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/* Violet button variants for mobile mode toggle */
|
|
36
|
-
.onekite-btn-violet {
|
|
37
|
-
background: #8e44ad !important;
|
|
38
|
-
border-color: #8e44ad !important;
|
|
39
|
-
color: #fff !important;
|
|
40
|
-
}
|
|
41
|
-
.onekite-btn-violet:hover {
|
|
42
|
-
filter: brightness(0.95);
|
|
43
|
-
color: #fff !important;
|
|
44
|
-
}
|
|
45
|
-
.onekite-btn-violet-outline {
|
|
46
|
-
background: transparent !important;
|
|
47
|
-
border-color: #8e44ad !important;
|
|
48
|
-
color: #8e44ad !important;
|
|
49
|
-
}
|
|
50
|
-
.onekite-btn-violet-outline:hover {
|
|
51
|
-
background: #8e44ad !important;
|
|
52
|
-
border-color: #8e44ad !important;
|
|
53
|
-
color: #fff !important;
|
|
54
|
-
}
|
|
55
30
|
|
|
56
31
|
/* Mobile tweaks for FullCalendar */
|
|
57
32
|
@media (max-width: 576px) {
|