nodebb-plugin-onekite-calendar 2.0.63 → 2.0.65
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 +52 -22
- package/lib/realtime.js +10 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/public/client.js +59 -2
package/lib/api.js
CHANGED
|
@@ -2,12 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
|
|
5
|
-
const meta = require.main.require('./src/meta');
|
|
6
5
|
const nconf = require.main.require('nconf');
|
|
7
6
|
const user = require.main.require('./src/user');
|
|
8
7
|
const groups = require.main.require('./src/groups');
|
|
9
|
-
const db = require.main.require('./src/database');
|
|
10
|
-
const logger = require.main.require('./src/logger');
|
|
11
8
|
|
|
12
9
|
const dbLayer = require('./db');
|
|
13
10
|
const { getSettings } = require('./settings');
|
|
@@ -251,25 +248,26 @@ async function canCreateSpecial(uid, settings) {
|
|
|
251
248
|
}
|
|
252
249
|
|
|
253
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Determines if a user can join or leave special events as a participant.
|
|
253
|
+
*
|
|
254
|
+
* This permission is intentionally permissive: any authenticated user can participate
|
|
255
|
+
* in special events, regardless of group membership. This differs from creation/deletion
|
|
256
|
+
* permissions which remain restricted to specific groups.
|
|
257
|
+
*
|
|
258
|
+
* @param {number|string} uid - The user ID. Falsy values (0, null, undefined, '') indicate guest/unauthenticated users.
|
|
259
|
+
* @param {Object} settings - Plugin settings (unused but kept for API consistency with other permission functions).
|
|
260
|
+
* @returns {boolean} True if the user can join/leave special events (i.e., is authenticated).
|
|
261
|
+
*
|
|
262
|
+
* @since 1.0.0 Modified to allow all authenticated users (previously restricted to specific groups)
|
|
263
|
+
*/
|
|
254
264
|
async function canJoinSpecial(uid, settings) {
|
|
255
|
-
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
const isAdmin = await groups.isMember(uid, 'administrators');
|
|
259
|
-
if (isAdmin) return true;
|
|
260
|
-
} catch (e) {}
|
|
261
|
-
|
|
262
|
-
// Special-event creators can join/leave
|
|
263
|
-
if (await canCreateSpecial(uid, settings)) return true;
|
|
264
|
-
|
|
265
|
-
// Location creator groups can also participate (even if they can't create events)
|
|
266
|
-
const allowed = normalizeAllowedGroups(settings.creatorGroups || '');
|
|
267
|
-
if (!allowed.length) return false;
|
|
268
|
-
return userInAnyGroup(uid, allowed);
|
|
265
|
+
// Any authenticated user (non-zero uid) can participate in special events.
|
|
266
|
+
// The !! coercion converts truthy values to true, falsy to false.
|
|
267
|
+
return !!uid;
|
|
269
268
|
}
|
|
270
269
|
|
|
271
270
|
|
|
272
|
-
|
|
273
271
|
async function canDeleteSpecial(uid, settings) {
|
|
274
272
|
if (!uid) return false;
|
|
275
273
|
try {
|
|
@@ -838,11 +836,19 @@ api.joinSpecialEvent = async function (req, res) {
|
|
|
838
836
|
ev.participants = JSON.stringify(list);
|
|
839
837
|
await dbLayer.saveSpecialEvent(ev);
|
|
840
838
|
|
|
839
|
+
const names = await usernamesByUids(list);
|
|
840
|
+
|
|
841
841
|
realtime.emitCalendarUpdated({ kind: 'specialEvent', action: 'participants', eid });
|
|
842
|
+
realtime.emitParticipantsUpdated({
|
|
843
|
+
type: 'special',
|
|
844
|
+
id: eid,
|
|
845
|
+
participants: list,
|
|
846
|
+
participantsUsernames: names,
|
|
847
|
+
});
|
|
842
848
|
return res.json({
|
|
843
849
|
ok: true,
|
|
844
850
|
participants: list,
|
|
845
|
-
participantsUsernames:
|
|
851
|
+
participantsUsernames: names,
|
|
846
852
|
});
|
|
847
853
|
};
|
|
848
854
|
|
|
@@ -865,8 +871,16 @@ api.leaveSpecialEvent = async function (req, res) {
|
|
|
865
871
|
ev.participants = JSON.stringify(next);
|
|
866
872
|
await dbLayer.saveSpecialEvent(ev);
|
|
867
873
|
|
|
874
|
+
const names = await usernamesByUids(next);
|
|
875
|
+
|
|
868
876
|
realtime.emitCalendarUpdated({ kind: 'specialEvent', action: 'participants', eid });
|
|
869
|
-
|
|
877
|
+
realtime.emitParticipantsUpdated({
|
|
878
|
+
type: 'special',
|
|
879
|
+
id: eid,
|
|
880
|
+
participants: next,
|
|
881
|
+
participantsUsernames: names,
|
|
882
|
+
});
|
|
883
|
+
return res.json({ ok: true, participants: next, participantsUsernames: names });
|
|
870
884
|
};
|
|
871
885
|
|
|
872
886
|
api.getCapabilities = async function (req, res) {
|
|
@@ -1032,8 +1046,16 @@ api.joinOuting = async function (req, res) {
|
|
|
1032
1046
|
o.participants = JSON.stringify(list);
|
|
1033
1047
|
await dbLayer.saveOuting(o);
|
|
1034
1048
|
|
|
1049
|
+
const names = await usernamesByUids(list);
|
|
1050
|
+
|
|
1035
1051
|
realtime.emitCalendarUpdated({ kind: 'outing', action: 'participants', oid });
|
|
1036
|
-
|
|
1052
|
+
realtime.emitParticipantsUpdated({
|
|
1053
|
+
type: 'outing',
|
|
1054
|
+
id: oid,
|
|
1055
|
+
participants: list,
|
|
1056
|
+
participantsUsernames: names,
|
|
1057
|
+
});
|
|
1058
|
+
return res.json({ ok: true, participants: list, participantsUsernames: names });
|
|
1037
1059
|
};
|
|
1038
1060
|
|
|
1039
1061
|
api.leaveOuting = async function (req, res) {
|
|
@@ -1056,8 +1078,16 @@ api.leaveOuting = async function (req, res) {
|
|
|
1056
1078
|
o.participants = JSON.stringify(next);
|
|
1057
1079
|
await dbLayer.saveOuting(o);
|
|
1058
1080
|
|
|
1081
|
+
const names = await usernamesByUids(next);
|
|
1082
|
+
|
|
1059
1083
|
realtime.emitCalendarUpdated({ kind: 'outing', action: 'participants', oid });
|
|
1060
|
-
|
|
1084
|
+
realtime.emitParticipantsUpdated({
|
|
1085
|
+
type: 'outing',
|
|
1086
|
+
id: oid,
|
|
1087
|
+
participants: next,
|
|
1088
|
+
participantsUsernames: names,
|
|
1089
|
+
});
|
|
1090
|
+
return res.json({ ok: true, participants: next, participantsUsernames: names });
|
|
1061
1091
|
};
|
|
1062
1092
|
|
|
1063
1093
|
api.createOuting = async function (req, res) {
|
package/lib/realtime.js
CHANGED
|
@@ -27,6 +27,16 @@ function emitCalendarUpdated(payload) {
|
|
|
27
27
|
} catch (e) {}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function emitParticipantsUpdated(payload) {
|
|
31
|
+
try {
|
|
32
|
+
const server = getIO();
|
|
33
|
+
if (!server || !server.sockets || typeof server.sockets.emit !== 'function') return;
|
|
34
|
+
|
|
35
|
+
server.sockets.emit('event:calendar-onekite.participantsUpdated', payload || {});
|
|
36
|
+
} catch (e) {}
|
|
37
|
+
}
|
|
38
|
+
|
|
30
39
|
module.exports = {
|
|
31
40
|
emitCalendarUpdated,
|
|
41
|
+
emitParticipantsUpdated,
|
|
32
42
|
};
|
package/package.json
CHANGED
package/plugin.json
CHANGED
package/public/client.js
CHANGED
|
@@ -1705,7 +1705,7 @@ function toDatetimeLocalValue(date) {
|
|
|
1705
1705
|
const leaveBtn = (canJoinHere && p.isParticipant)
|
|
1706
1706
|
? `<button type="button" class="btn btn-sm btn-danger ms-2 onekite-leave-special" data-eid="${eidPlain}" title="Se retirer" aria-label="Se retirer">−</button>`
|
|
1707
1707
|
: '';
|
|
1708
|
-
const participantsHtml = `<div class="mb-2" id="onekite-participants-special"><strong>Participants</strong>${joinBtn}${leaveBtn}<br>` +
|
|
1708
|
+
const participantsHtml = `<div class="mb-2" id="onekite-participants-special" data-eid="${eidPlain}"><strong>Participants</strong>${joinBtn}${leaveBtn}<br>` +
|
|
1709
1709
|
(participants.length
|
|
1710
1710
|
? participants.map((name) => {
|
|
1711
1711
|
const u = String(name || '').trim();
|
|
@@ -1855,7 +1855,7 @@ function toDatetimeLocalValue(date) {
|
|
|
1855
1855
|
const leaveBtn = (canJoinHere && p.isParticipant)
|
|
1856
1856
|
? `<button type="button" class="btn btn-sm btn-danger ms-2 onekite-leave-outing" data-oid="${oidPlain}" title="Se retirer" aria-label="Se retirer">−</button>`
|
|
1857
1857
|
: '';
|
|
1858
|
-
const participantsHtml = `<div class="mb-2" id="onekite-participants-outing"><strong>Participants</strong>${joinBtn}${leaveBtn}<br>` +
|
|
1858
|
+
const participantsHtml = `<div class="mb-2" id="onekite-participants-outing" data-oid="${oidPlain}"><strong>Participants</strong>${joinBtn}${leaveBtn}<br>` +
|
|
1859
1859
|
(participants.length
|
|
1860
1860
|
? participants.map((name) => {
|
|
1861
1861
|
const u = String(name || '').trim();
|
|
@@ -2846,6 +2846,63 @@ try {
|
|
|
2846
2846
|
scheduleRefetch(cal);
|
|
2847
2847
|
} catch (e) {}
|
|
2848
2848
|
});
|
|
2849
|
+
|
|
2850
|
+
// Live update of participants list in open modals (special events & outings)
|
|
2851
|
+
socket.on('event:calendar-onekite.participantsUpdated', function (payload) {
|
|
2852
|
+
try {
|
|
2853
|
+
const p = payload || {};
|
|
2854
|
+
const type = String(p.type || '').trim();
|
|
2855
|
+
const id = String(p.id || '').trim();
|
|
2856
|
+
if (!type || !id) return;
|
|
2857
|
+
|
|
2858
|
+
const names = Array.isArray(p.participantsUsernames) ? p.participantsUsernames : [];
|
|
2859
|
+
const uids = Array.isArray(p.participants) ? p.participants.map(String) : [];
|
|
2860
|
+
|
|
2861
|
+
const uidNow = String(
|
|
2862
|
+
(window.config && window.config.uid) ||
|
|
2863
|
+
(typeof app !== 'undefined' && app && app.user && app.user.uid) ||
|
|
2864
|
+
(typeof ajaxify !== 'undefined' && ajaxify && ajaxify.data && ajaxify.data.uid) ||
|
|
2865
|
+
''
|
|
2866
|
+
);
|
|
2867
|
+
const isParticipant = uidNow && uids.includes(String(uidNow));
|
|
2868
|
+
|
|
2869
|
+
const sel = (type === 'special') ? '#onekite-participants-special' : (type === 'outing') ? '#onekite-participants-outing' : '';
|
|
2870
|
+
if (!sel) return;
|
|
2871
|
+
const candidates = document.querySelectorAll(sel);
|
|
2872
|
+
if (!candidates || !candidates.length) return;
|
|
2873
|
+
let box = null;
|
|
2874
|
+
for (const el of candidates) {
|
|
2875
|
+
if (!el) continue;
|
|
2876
|
+
const matchId = type === 'special' ? String(el.getAttribute('data-eid') || '') : String(el.getAttribute('data-oid') || '');
|
|
2877
|
+
if (matchId === id) { box = el; break; }
|
|
2878
|
+
}
|
|
2879
|
+
if (!box) return;
|
|
2880
|
+
|
|
2881
|
+
const hasControls = !!box.querySelector('.onekite-join-special, .onekite-leave-special, .onekite-join-outing, .onekite-leave-outing');
|
|
2882
|
+
let controlsHtml = '';
|
|
2883
|
+
if (hasControls && uidNow) {
|
|
2884
|
+
if (type === 'special') {
|
|
2885
|
+
controlsHtml = isParticipant
|
|
2886
|
+
? `<button type="button" class="btn btn-sm btn-danger ms-2 onekite-leave-special" data-eid="${escapeHtml(id)}" title="Se retirer" aria-label="Se retirer">−</button>`
|
|
2887
|
+
: `<button type="button" class="btn btn-sm btn-success ms-2 onekite-join-special" data-eid="${escapeHtml(id)}" title="S'ajouter" aria-label="S'ajouter">+</button>`;
|
|
2888
|
+
} else if (type === 'outing') {
|
|
2889
|
+
controlsHtml = isParticipant
|
|
2890
|
+
? `<button type="button" class="btn btn-sm btn-danger ms-2 onekite-leave-outing" data-oid="${escapeHtml(id)}" title="Se retirer" aria-label="Se retirer">−</button>`
|
|
2891
|
+
: `<button type="button" class="btn btn-sm btn-success ms-2 onekite-join-outing" data-oid="${escapeHtml(id)}" title="S'ajouter" aria-label="S'ajouter">+</button>`;
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
const links = names.length
|
|
2896
|
+
? names.map((name) => {
|
|
2897
|
+
const u = String(name || '').trim();
|
|
2898
|
+
if (!u) return '';
|
|
2899
|
+
return `<a class="onekite-user-link me-2" href="${window.location.origin}/user/${encodeURIComponent(u)}">${escapeHtml(u)}</a>`;
|
|
2900
|
+
}).filter(Boolean).join('')
|
|
2901
|
+
: `<span class="text-muted">Aucun</span>`;
|
|
2902
|
+
|
|
2903
|
+
box.innerHTML = `<strong>Participants</strong>${controlsHtml}<br>${links}`;
|
|
2904
|
+
} catch (e) {}
|
|
2905
|
+
});
|
|
2849
2906
|
}
|
|
2850
2907
|
} catch (e) {}
|
|
2851
2908
|
|