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 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, lang, params);
26
+ await emailer.sendToEmail(template, toEmail, subject, data);
34
27
  } else {
35
- await emailer.sendToEmail(template, toEmail, params);
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, lang, params);
35
+ await emailer.send(template, toEmail, subject, data);
42
36
  return;
43
37
  }
44
38
  if (emailer.send.length === 3) {
45
- await emailer.send(template, toEmail, params);
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, lang, params);
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((err && err.message) || err) });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-calendar-onekite",
3
- "version": "11.1.93",
3
+ "version": "11.1.95",
4
4
  "description": "FullCalendar-based equipment reservation workflow with admin approval & HelloAsso payment for NodeBB",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
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: () => __settle(null),
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
- __settle({ title, start: startVal, end: endVal, address, notes, lat, lon });
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
- __settle(null);
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
- __settle({ itemIds, itemNames, total, days });
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 mode = 'reservation'; // or 'special'
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
- mode = (mode === 'special') ? 'reservation' : 'special';
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
- mode = 'reservation';
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(formatPeriodWithTime(ev.start, ev.end))}</div>
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
- // Sync mode UI after the header is rendered
1212
- try { refreshModeIndicators(); } catch (e) {}
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-outline onekite-mode-btn';
1262
- // Expose to the shared mode indicator refresher
1263
- mobileModeBtn = modeBtn;
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
- mode = (mode === 'special') ? 'reservation' : 'special';
1266
- refreshModeIndicators();
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
- refreshModeIndicators();
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) {