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 +39 -5
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/public/client.js +25 -30
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 || '
|
|
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 || '
|
|
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
|
-
|
|
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() || '
|
|
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 || '
|
|
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
package/plugin.json
CHANGED
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
|
-
/*
|
|
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: #
|
|
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: #
|
|
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: #
|
|
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
|
-
|
|
298
|
-
|
|
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"
|
|
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
|
-
|
|
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
|
-
|
|
1545
|
-
el2.style.
|
|
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: '
|
|
1694
|
+
title: 'Sortie',
|
|
1700
1695
|
message: html,
|
|
1701
1696
|
buttons: {
|
|
1702
1697
|
close: { label: 'Fermer', className: 'btn-secondary' },
|