nodebb-plugin-calendar-onekite 11.1.87 → 11.1.89
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
CHANGED
package/public/client.js
CHANGED
|
@@ -51,7 +51,7 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
51
51
|
`;
|
|
52
52
|
|
|
53
53
|
return await new Promise((resolve) => {
|
|
54
|
-
bootbox.dialog({
|
|
54
|
+
const dialog = bootbox.dialog({
|
|
55
55
|
title: 'Créer un évènement',
|
|
56
56
|
message: html,
|
|
57
57
|
buttons: {
|
|
@@ -79,13 +79,17 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
// init leaflet
|
|
82
|
+
dialog.on('shown.bs.modal', () => {
|
|
82
83
|
setTimeout(async () => {
|
|
84
|
+
|
|
83
85
|
try {
|
|
84
86
|
const mapEl = document.getElementById('onekite-se-map');
|
|
85
87
|
if (!mapEl) return;
|
|
86
88
|
const L = await loadLeaflet();
|
|
87
89
|
const map = L.map(mapEl).setView([46.5, 2.5], 5);
|
|
88
90
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(map);
|
|
91
|
+
setTimeout(() => { try { map.invalidateSize(); } catch (e) {} }, 100);
|
|
92
|
+
setTimeout(() => { try { map.invalidateSize(); } catch (e) {} }, 100);
|
|
89
93
|
let marker = null;
|
|
90
94
|
function setMarker(lat, lon) {
|
|
91
95
|
if (marker) map.removeLayer(marker);
|
|
@@ -114,7 +118,8 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
114
118
|
} catch (e) {
|
|
115
119
|
// ignore leaflet errors
|
|
116
120
|
}
|
|
117
|
-
|
|
121
|
+
}, 50);
|
|
122
|
+
});
|
|
118
123
|
});
|
|
119
124
|
}
|
|
120
125
|
|
|
@@ -125,18 +130,21 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
125
130
|
${safeAddr ? `<div class="mb-2">${escapeHtml(safeAddr)}</div>` : ''}
|
|
126
131
|
<div id="${mapId}" style="height:260px; border:1px solid #ddd; border-radius:6px;"></div>
|
|
127
132
|
`;
|
|
128
|
-
bootbox.dialog({
|
|
133
|
+
const dialog = bootbox.dialog({
|
|
129
134
|
title: title || 'Carte',
|
|
130
135
|
message: html,
|
|
131
136
|
buttons: { close: { label: 'Fermer', className: 'btn-secondary' } },
|
|
132
137
|
});
|
|
138
|
+
dialog.on('shown.bs.modal', () => {
|
|
133
139
|
setTimeout(async () => {
|
|
140
|
+
|
|
134
141
|
try {
|
|
135
142
|
const el = document.getElementById(mapId);
|
|
136
143
|
if (!el) return;
|
|
137
144
|
const L = await loadLeaflet();
|
|
138
145
|
const map = L.map(el);
|
|
139
146
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(map);
|
|
147
|
+
setTimeout(() => { try { map.invalidateSize(); } catch (e) {} }, 100);
|
|
140
148
|
|
|
141
149
|
async function setAt(lat2, lon2) {
|
|
142
150
|
map.setView([lat2, lon2], 14);
|
|
@@ -159,7 +167,8 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
159
167
|
} catch (e) {
|
|
160
168
|
// ignore leaflet errors
|
|
161
169
|
}
|
|
162
|
-
|
|
170
|
+
}, 50);
|
|
171
|
+
});
|
|
163
172
|
}
|
|
164
173
|
|
|
165
174
|
// Click handler for map links in popups
|
|
@@ -342,7 +351,14 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
342
351
|
}
|
|
343
352
|
|
|
344
353
|
|
|
345
|
-
|
|
354
|
+
|
|
355
|
+
function toLocalYmd(date) {
|
|
356
|
+
const d = new Date(date);
|
|
357
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
358
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function toDatetimeLocalValue(date) {
|
|
346
362
|
const d = new Date(date);
|
|
347
363
|
const pad = (n) => String(n).padStart(2, '0');
|
|
348
364
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
@@ -474,14 +490,70 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
474
490
|
|
|
475
491
|
let mode = 'reservation'; // or 'special'
|
|
476
492
|
|
|
493
|
+
// Inject lightweight responsive CSS once.
|
|
494
|
+
try {
|
|
495
|
+
const styleId = 'onekite-responsive-css';
|
|
496
|
+
if (!document.getElementById(styleId)) {
|
|
497
|
+
const st = document.createElement('style');
|
|
498
|
+
st.id = styleId;
|
|
499
|
+
st.textContent = `
|
|
500
|
+
/* Prevent accidental horizontal scroll on small screens */
|
|
501
|
+
#onekite-calendar, #onekite-calendar * { box-sizing: border-box; }
|
|
502
|
+
#onekite-calendar { width: 100%; max-width: 100%; overflow-x: hidden; }
|
|
503
|
+
|
|
504
|
+
@media (max-width: 576px) {
|
|
505
|
+
#onekite-calendar .fc .fc-toolbar-title { font-size: 1.05rem; }
|
|
506
|
+
#onekite-calendar .fc .fc-button { padding: .25rem .45rem; font-size: .85rem; }
|
|
507
|
+
#onekite-calendar .fc .fc-daygrid-day-number { font-size: .8rem; }
|
|
508
|
+
#onekite-calendar .fc .fc-daygrid-event { font-size: .75rem; }
|
|
509
|
+
#onekite-calendar .fc .fc-timegrid-event { font-size: .75rem; }
|
|
510
|
+
#onekite-calendar .fc .fc-col-header-cell-cushion { font-size: .8rem; }
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
@media (max-width: 576px) and (orientation: landscape) {
|
|
514
|
+
#onekite-calendar .fc .fc-toolbar-title { font-size: 1rem; }
|
|
515
|
+
#onekite-calendar .fc .fc-button { padding: .2rem .4rem; font-size: .8rem; }
|
|
516
|
+
}
|
|
517
|
+
`;
|
|
518
|
+
document.head.appendChild(st);
|
|
519
|
+
}
|
|
520
|
+
} catch (e) {}
|
|
521
|
+
|
|
522
|
+
function isMobileNow() {
|
|
523
|
+
return !!(window.matchMedia && window.matchMedia('(max-width: 576px)').matches);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function isLandscapeNow() {
|
|
527
|
+
return !!(window.matchMedia && window.matchMedia('(orientation: landscape)').matches);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function computeAspectRatio() {
|
|
531
|
+
const mobile = isMobileNow();
|
|
532
|
+
if (!mobile) return 1.35;
|
|
533
|
+
return isLandscapeNow() ? 1.6 : 0.9;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const headerToolbar = isMobileNow() ? {
|
|
537
|
+
left: 'prev,next',
|
|
538
|
+
center: 'title',
|
|
539
|
+
right: 'today',
|
|
540
|
+
} : {
|
|
541
|
+
left: 'prev,next today',
|
|
542
|
+
center: 'title',
|
|
543
|
+
// Only month + week (no day view)
|
|
544
|
+
right: (canCreateSpecial ? 'newSpecial ' : '') + 'dayGridMonth,timeGridWeek',
|
|
545
|
+
};
|
|
546
|
+
|
|
477
547
|
const calendar = new FullCalendar.Calendar(el, {
|
|
478
548
|
initialView: 'dayGridMonth',
|
|
549
|
+
height: 'auto',
|
|
550
|
+
contentHeight: 'auto',
|
|
551
|
+
aspectRatio: computeAspectRatio(),
|
|
552
|
+
dayMaxEvents: true,
|
|
479
553
|
locale: 'fr',
|
|
480
|
-
headerToolbar:
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
right: (canCreateSpecial ? 'newSpecial ' : '') + 'dayGridMonth,timeGridWeek,timeGridDay',
|
|
484
|
-
},
|
|
554
|
+
headerToolbar: headerToolbar,
|
|
555
|
+
// Keep titles short on mobile to avoid horizontal overflow
|
|
556
|
+
titleFormat: isMobileNow() ? { year: 'numeric', month: 'short' } : undefined,
|
|
485
557
|
customButtons: canCreateSpecial ? {
|
|
486
558
|
newSpecial: {
|
|
487
559
|
text: 'Évènement',
|
|
@@ -547,8 +619,8 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
547
619
|
return;
|
|
548
620
|
}
|
|
549
621
|
// Send date strings (no hours) so reservations are day-based.
|
|
550
|
-
const startDate =
|
|
551
|
-
const endDate =
|
|
622
|
+
const startDate = toLocalYmd(info.start);
|
|
623
|
+
const endDate = toLocalYmd(info.end);
|
|
552
624
|
await requestReservation({
|
|
553
625
|
start: startDate,
|
|
554
626
|
end: endDate,
|
|
@@ -893,6 +965,92 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
893
965
|
try { window.oneKiteCalendar = calendar; } catch (e) {}
|
|
894
966
|
|
|
895
967
|
calendar.render();
|
|
968
|
+
|
|
969
|
+
// Mobile controls: view (month/week) + mode (reservation/event) without bloating the header.
|
|
970
|
+
try {
|
|
971
|
+
const controlsId = 'onekite-mobile-controls';
|
|
972
|
+
const old = document.getElementById(controlsId);
|
|
973
|
+
if (old) old.remove();
|
|
974
|
+
|
|
975
|
+
if (isMobileNow()) {
|
|
976
|
+
const controls = document.createElement('div');
|
|
977
|
+
controls.id = controlsId;
|
|
978
|
+
controls.className = 'mt-2 d-flex flex-wrap gap-2 align-items-center';
|
|
979
|
+
|
|
980
|
+
const viewGroup = document.createElement('div');
|
|
981
|
+
viewGroup.className = 'btn-group btn-group-sm';
|
|
982
|
+
viewGroup.setAttribute('role', 'group');
|
|
983
|
+
|
|
984
|
+
const btnMonth = document.createElement('button');
|
|
985
|
+
btnMonth.type = 'button';
|
|
986
|
+
btnMonth.className = 'btn btn-outline-secondary';
|
|
987
|
+
btnMonth.textContent = 'Mois';
|
|
988
|
+
|
|
989
|
+
const btnWeek = document.createElement('button');
|
|
990
|
+
btnWeek.type = 'button';
|
|
991
|
+
btnWeek.className = 'btn btn-outline-secondary';
|
|
992
|
+
btnWeek.textContent = 'Semaine';
|
|
993
|
+
|
|
994
|
+
function refreshViewButtons() {
|
|
995
|
+
const v = calendar.view && calendar.view.type;
|
|
996
|
+
btnMonth.classList.toggle('active', v === 'dayGridMonth');
|
|
997
|
+
btnWeek.classList.toggle('active', v === 'timeGridWeek');
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
btnMonth.addEventListener('click', () => {
|
|
1001
|
+
calendar.changeView('dayGridMonth');
|
|
1002
|
+
refreshViewButtons();
|
|
1003
|
+
});
|
|
1004
|
+
btnWeek.addEventListener('click', () => {
|
|
1005
|
+
calendar.changeView('timeGridWeek');
|
|
1006
|
+
refreshViewButtons();
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
viewGroup.appendChild(btnMonth);
|
|
1010
|
+
viewGroup.appendChild(btnWeek);
|
|
1011
|
+
controls.appendChild(viewGroup);
|
|
1012
|
+
|
|
1013
|
+
if (canCreateSpecial) {
|
|
1014
|
+
const modeBtn = document.createElement('button');
|
|
1015
|
+
modeBtn.type = 'button';
|
|
1016
|
+
modeBtn.className = 'btn btn-sm btn-outline-primary';
|
|
1017
|
+
function refreshModeBtn() {
|
|
1018
|
+
const isSpecial = mode === 'special';
|
|
1019
|
+
modeBtn.textContent = isSpecial ? 'Mode évènement ✓' : 'Mode évènement';
|
|
1020
|
+
modeBtn.classList.toggle('active', isSpecial);
|
|
1021
|
+
}
|
|
1022
|
+
modeBtn.addEventListener('click', () => {
|
|
1023
|
+
mode = (mode === 'special') ? 'reservation' : 'special';
|
|
1024
|
+
refreshModeBtn();
|
|
1025
|
+
if (mode === 'special') {
|
|
1026
|
+
showAlert('success', 'Mode évènement : sélectionne une plage (date/heure) sur le calendrier.');
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
refreshModeBtn();
|
|
1030
|
+
controls.appendChild(modeBtn);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
el.parentNode && el.parentNode.insertBefore(controls, el.nextSibling);
|
|
1034
|
+
refreshViewButtons();
|
|
1035
|
+
}
|
|
1036
|
+
} catch (e) {}
|
|
1037
|
+
|
|
1038
|
+
// Update sizing when rotating/resizing (especially on mobile landscape).
|
|
1039
|
+
try {
|
|
1040
|
+
let lastMobile = isMobileNow();
|
|
1041
|
+
let lastLandscape = isLandscapeNow();
|
|
1042
|
+
window.addEventListener('resize', () => {
|
|
1043
|
+
const mobile = isMobileNow();
|
|
1044
|
+
const landscape = isLandscapeNow();
|
|
1045
|
+
if (mobile !== lastMobile || landscape !== lastLandscape) {
|
|
1046
|
+
lastMobile = mobile;
|
|
1047
|
+
lastLandscape = landscape;
|
|
1048
|
+
calendar.setOption('aspectRatio', computeAspectRatio());
|
|
1049
|
+
calendar.setOption('titleFormat', mobile ? { year: 'numeric', month: 'short' } : undefined);
|
|
1050
|
+
}
|
|
1051
|
+
try { calendar.updateSize(); } catch (err) {}
|
|
1052
|
+
});
|
|
1053
|
+
} catch (e) {}
|
|
896
1054
|
}
|
|
897
1055
|
|
|
898
1056
|
// Auto-init on /calendar when ajaxify finishes rendering.
|
|
@@ -48,6 +48,12 @@
|
|
|
48
48
|
<input class="form-control" name="pendingHoldMinutes" placeholder="5">
|
|
49
49
|
</div>
|
|
50
50
|
|
|
51
|
+
<div class="mb-3">
|
|
52
|
+
<label class="form-label">Délai rappel paiement (minutes)</label>
|
|
53
|
+
<input class="form-control" name="paymentHoldMinutes" placeholder="60">
|
|
54
|
+
<div class="form-text">Après validation (statut <code>paiement en attente</code>), un rappel est envoyé après ce délai. La réservation est ensuite expirée après <strong>2×</strong> ce délai.</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
51
57
|
<h4 class="mt-4">HelloAsso</h4>
|
|
52
58
|
|
|
53
59
|
<div class="mb-3">
|
|
@@ -15,3 +15,22 @@
|
|
|
15
15
|
No inline require() here.
|
|
16
16
|
The plugin's forum script auto-initialises on the calendar page via ajaxify.
|
|
17
17
|
-->
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
<style>
|
|
21
|
+
/* Mobile tweaks for FullCalendar */
|
|
22
|
+
@media (max-width: 576px) {
|
|
23
|
+
#onekite-calendar { margin-top: .5rem !important; }
|
|
24
|
+
.fc .fc-toolbar { flex-wrap: wrap; gap: .25rem; }
|
|
25
|
+
.fc .fc-toolbar-title { font-size: 1.05rem; line-height: 1.2; }
|
|
26
|
+
.fc .fc-button { padding: .25rem .45rem; font-size: .75rem; }
|
|
27
|
+
.fc .fc-button .fc-icon { font-size: .9em; }
|
|
28
|
+
.fc .fc-daygrid-day-number { font-size: .75rem; padding: 2px; }
|
|
29
|
+
.fc .fc-col-header-cell-cushion { font-size: .75rem; padding: 4px 0; }
|
|
30
|
+
.fc .fc-event-title { font-size: .72rem; }
|
|
31
|
+
/* Avoid horizontal scroll */
|
|
32
|
+
.fc { overflow-x: hidden; }
|
|
33
|
+
.fc .fc-scrollgrid, .fc .fc-scrollgrid table { width: 100% !important; }
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
36
|
+
|