nodebb-plugin-onekite-calendar 2.0.46 → 2.0.48

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/api.js CHANGED
@@ -507,7 +507,7 @@ function eventsForOuting(o) {
507
507
  type: 'outing',
508
508
  id: String(o.oid),
509
509
  uid: Number(o.uid) || 0,
510
- title: String(o.title || 'Prévision de sortie'),
510
+ title: String(o.title || 'Sortie'),
511
511
  details: String(o.notes || ''),
512
512
  location: String(o.address || ''),
513
513
  allDay: false,
@@ -516,7 +516,7 @@ function eventsForOuting(o) {
516
516
  });
517
517
  return {
518
518
  id: `outing:${o.oid}`,
519
- title: `${o.title || 'Prévision de sortie'}`.trim(),
519
+ title: `${o.title || 'Sortie'}`.trim(),
520
520
  allDay: false,
521
521
  start: startIso,
522
522
  end: endIso,
@@ -882,6 +882,19 @@ api.getSpecialEventDetails = async function (req, res) {
882
882
  const ev = await dbLayer.getSpecialEvent(eid);
883
883
  if (!ev) return res.status(404).json({ error: 'not-found' });
884
884
 
885
+ // Calendar export links (ICS / Google Calendar)
886
+ const links = buildCalendarLinks({
887
+ type: 'special',
888
+ id: String(ev.eid),
889
+ uid: Number(ev.uid) || 0,
890
+ title: String(ev.title || 'Évènement'),
891
+ details: String(ev.notes || ''),
892
+ location: String(ev.address || ''),
893
+ allDay: false,
894
+ start: new Date(parseInt(ev.start, 10)),
895
+ end: new Date(parseInt(ev.end, 10)),
896
+ });
897
+
885
898
  // Anyone who can see the calendar can view special events, but creator username
886
899
  // is only visible to moderators/allowed users or the creator.
887
900
  const out = {
@@ -894,6 +907,8 @@ api.getSpecialEventDetails = async function (req, res) {
894
907
  lon: ev.lon || '',
895
908
  notes: ev.notes || '',
896
909
  canDeleteSpecial: canSpecialDelete,
910
+ icsUrl: links.icsUrl,
911
+ googleCalUrl: links.googleCalUrl,
897
912
  };
898
913
  if (ev.username && (canMod || canSpecialDelete || (uid && String(uid) === String(ev.uid)))) {
899
914
  out.username = String(ev.username);
@@ -998,6 +1013,19 @@ api.getOutingDetails = async function (req, res) {
998
1013
  const o = await dbLayer.getOuting(oid);
999
1014
  if (!o) return res.status(404).json({ error: 'not-found' });
1000
1015
 
1016
+ // Calendar export links (ICS / Google Calendar)
1017
+ const links = buildCalendarLinks({
1018
+ type: 'outing',
1019
+ id: String(o.oid),
1020
+ uid: Number(o.uid) || 0,
1021
+ title: String(o.title || 'Sortie'),
1022
+ details: String(o.notes || ''),
1023
+ location: String(o.address || ''),
1024
+ allDay: false,
1025
+ start: new Date(parseInt(o.start, 10)),
1026
+ end: new Date(parseInt(o.end, 10)),
1027
+ });
1028
+
1001
1029
  const out = {
1002
1030
  oid: o.oid,
1003
1031
  title: o.title || '',
@@ -1008,6 +1036,8 @@ api.getOutingDetails = async function (req, res) {
1008
1036
  lon: o.lon || '',
1009
1037
  notes: o.notes || '',
1010
1038
  canDeleteOuting: canMod || (uid && String(uid) === String(o.uid)),
1039
+ icsUrl: links.icsUrl,
1040
+ googleCalUrl: links.googleCalUrl,
1011
1041
  };
1012
1042
  if (o.username && (canMod || (uid && String(uid) === String(o.uid)))) {
1013
1043
  out.username = String(o.username);
@@ -1020,10 +1050,14 @@ api.createOuting = async function (req, res) {
1020
1050
  if (!req.uid) return res.status(401).json({ error: 'not-logged-in' });
1021
1051
 
1022
1052
  const startTs = toTs(req.body && req.body.start);
1023
- const ok = await canRequest(req.uid, settings, startTs);
1053
+ // Permissions for outings must match reservations/locations rights.
1054
+ // We intentionally base the "auto" yearly group on the *current* year,
1055
+ // not on the outing date, so members can plan future outings without
1056
+ // requiring next-year group membership.
1057
+ const ok = await canRequest(req.uid, settings, Date.now());
1024
1058
  if (!ok) return res.status(403).json({ error: 'not-allowed' });
1025
1059
 
1026
- const title = String((req.body && req.body.title) || '').trim() || 'Prévision de sortie';
1060
+ const title = String((req.body && req.body.title) || '').trim() || 'Sortie';
1027
1061
  const endTs = toTs(req.body && req.body.end);
1028
1062
  if (!Number.isFinite(startTs) || !Number.isFinite(endTs) || !(startTs < endTs)) {
1029
1063
  return res.status(400).json({ error: 'bad-dates' });
@@ -1934,7 +1968,7 @@ api.getIcs = async function (req, res) {
1934
1968
  const end = new Date(parseInt(o.end, 10));
1935
1969
  ics = buildIcs({
1936
1970
  uid: `outing-${id}@onekite`,
1937
- summary: String(o.title || 'Prévision de sortie'),
1971
+ summary: String(o.title || 'Sortie'),
1938
1972
  description: String(o.notes || ''),
1939
1973
  location: String(o.address || ''),
1940
1974
  allDay: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-onekite-calendar",
3
- "version": "2.0.46",
3
+ "version": "2.0.48",
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/plugin.json CHANGED
@@ -39,5 +39,5 @@
39
39
  "acpScripts": [
40
40
  "public/admin.js"
41
41
  ],
42
- "version": "2.0.46"
42
+ "version": "2.0.48"
43
43
  }
package/public/client.js CHANGED
@@ -62,33 +62,24 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
62
62
  }
63
63
  }
64
64
 
65
- /* Calendar item action buttons (match calendar colors) */
65
+ /* Creation chooser buttons: outline only, match calendar colors */
66
+ .btn-onekite-special,
67
+ .btn-onekite-outing,
68
+ .btn-onekite-location {
69
+ background: transparent;
70
+ border-width: 2px;
71
+ }
66
72
  .btn-onekite-special {
67
- background: #8e44ad;
68
73
  border-color: #8e44ad;
69
- color: #fff;
70
- }
71
- .btn-onekite-special:hover {
72
- filter: brightness(0.95);
73
- color: #fff;
74
+ color: #8e44ad;
74
75
  }
75
76
  .btn-onekite-outing {
76
- background: #2980b9;
77
77
  border-color: #2980b9;
78
- color: #fff;
79
- }
80
- .btn-onekite-outing:hover {
81
- filter: brightness(0.95);
82
- color: #fff;
78
+ color: #2980b9;
83
79
  }
84
80
  .btn-onekite-location {
85
- background: #27ae60;
86
81
  border-color: #27ae60;
87
- color: #fff;
88
- }
89
- .btn-onekite-location:hover {
90
- filter: brightness(0.95);
91
- color: #fff;
82
+ color: #27ae60;
92
83
  }
93
84
 
94
85
  /* Mobile FAB date range picker (single calendar) */
@@ -294,8 +285,10 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
294
285
 
295
286
  const seStartTime = timeString(seStart);
296
287
  const seEndTime = timeString(seEnd);
297
- const defaultTitlePlaceholder = kind === 'outing' ? 'Prévision de sortie' : 'Ex: ...';
298
- const defaultTitleValue = kind === 'outing' ? 'Prévision de sortie' : '';
288
+ // Same UX for all types: an example placeholder, but never pre-fill the value.
289
+ // If left empty, the server can still apply its own default label.
290
+ const defaultTitlePlaceholder = 'Ex: ...';
291
+ const defaultTitleValue = '';
299
292
 
300
293
  const html = `
301
294
  <div class="mb-3">
@@ -472,8 +465,6 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
472
465
  // a different title.
473
466
  const payload = await openSpecialEventDialog(selectionInfo, { kind: 'outing' });
474
467
  if (!payload) return null;
475
- // Default title if empty
476
- if (!payload.title) payload.title = 'Prévision de sortie';
477
468
  return payload;
478
469
  }
479
470
 
@@ -1503,12 +1494,13 @@ function toDatetimeLocalValue(date) {
1503
1494
  }
1504
1495
  } catch (e) {}
1505
1496
 
1506
- // IMPORTANT: align "special" event display exactly like reservation icons.
1497
+ // IMPORTANT: align "special" events and "outings" display exactly like
1498
+ // reservation icons.
1507
1499
  // We inject the clock + time range directly into the event title so FC
1508
1500
  // doesn't reserve a separate time column (which creates a leading gap).
1509
1501
  const mapped = (Array.isArray(data) ? data : []).map((ev) => {
1510
1502
  try {
1511
- if (ev && ev.extendedProps && ev.extendedProps.type === 'special' && ev.start && ev.end) {
1503
+ if (ev && ev.extendedProps && (ev.extendedProps.type === 'special' || ev.extendedProps.type === 'outing') && ev.start && ev.end) {
1512
1504
  // Force the same visual layout as reservations in month view
1513
1505
  // (avoid the "dot" layout which introduces a leading gap).
1514
1506
  ev.display = 'block';
@@ -1534,15 +1526,18 @@ function toDatetimeLocalValue(date) {
1534
1526
  }
1535
1527
  },
1536
1528
  eventDidMount: function (arg) {
1537
- // Keep special event colors consistent.
1529
+ // Keep special event + outing colors consistent even if a theme overrides
1530
+ // FullCalendar defaults.
1538
1531
  try {
1539
1532
  const ev = arg && arg.event;
1540
1533
  if (!ev) return;
1541
- if (ev.extendedProps && ev.extendedProps.type === 'special') {
1534
+ const t = ev.extendedProps && ev.extendedProps.type;
1535
+ if (t === 'special' || t === 'outing') {
1542
1536
  const el2 = arg.el;
1543
1537
  if (el2 && el2.style) {
1544
- el2.style.backgroundColor = '#8e44ad';
1545
- el2.style.borderColor = '#8e44ad';
1538
+ const bg = (t === 'outing') ? '#2980b9' : '#8e44ad';
1539
+ el2.style.backgroundColor = bg;
1540
+ el2.style.borderColor = bg;
1546
1541
  el2.style.color = '#ffffff';
1547
1542
  }
1548
1543
  }
@@ -1696,7 +1691,7 @@ function toDatetimeLocalValue(date) {
1696
1691
  `;
1697
1692
  const canDel = !!(p.canDeleteOuting);
1698
1693
  const dlg = bootbox.dialog({
1699
- title: 'Prévision de sortie',
1694
+ title: 'Sortie',
1700
1695
  message: html,
1701
1696
  buttons: {
1702
1697
  close: { label: 'Fermer', className: 'btn-secondary' },