nodebb-plugin-onekite-calendar 2.0.56 → 2.0.58

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
@@ -996,6 +996,29 @@ api.joinSpecialEvent = async function (req, res) {
996
996
  });
997
997
  };
998
998
 
999
+ api.leaveSpecialEvent = async function (req, res) {
1000
+ const settings = await getSettings();
1001
+ const uid = req.uid;
1002
+ if (!uid) return res.status(401).json({ error: 'not-logged-in' });
1003
+ const ok = await canCreateSpecial(uid, settings);
1004
+ if (!ok) return res.status(403).json({ error: 'not-allowed' });
1005
+
1006
+ const eid = String(req.params.eid || '').replace(/^special:/, '').trim();
1007
+ if (!eid) return res.status(400).json({ error: 'bad-id' });
1008
+
1009
+ const ev = await dbLayer.getSpecialEvent(eid);
1010
+ if (!ev) return res.status(404).json({ error: 'not-found' });
1011
+
1012
+ const list = normalizeUidList(ev.participants);
1013
+ const sUid = String(uid);
1014
+ const next = list.filter((x) => String(x) !== sUid);
1015
+ ev.participants = JSON.stringify(next);
1016
+ await dbLayer.saveSpecialEvent(ev);
1017
+
1018
+ realtime.emitCalendarUpdated({ kind: 'specialEvent', action: 'participants', eid });
1019
+ return res.json({ ok: true, participants: next, participantsUsernames: await usernamesByUids(next) });
1020
+ };
1021
+
999
1022
  api.getCapabilities = async function (req, res) {
1000
1023
  const settings = await getSettings();
1001
1024
  const uid = req.uid || 0;
@@ -1163,6 +1186,30 @@ api.joinOuting = async function (req, res) {
1163
1186
  return res.json({ ok: true, participants: list, participantsUsernames: await usernamesByUids(list) });
1164
1187
  };
1165
1188
 
1189
+ api.leaveOuting = async function (req, res) {
1190
+ const settings = await getSettings();
1191
+ const uid = req.uid;
1192
+ if (!uid) return res.status(401).json({ error: 'not-logged-in' });
1193
+ // Outings share the same rights as reservations/locations.
1194
+ const ok = await canRequest(uid, settings, Date.now());
1195
+ if (!ok) return res.status(403).json({ error: 'not-allowed' });
1196
+
1197
+ const oid = String(req.params.oid || '').replace(/^outing:/, '').trim();
1198
+ if (!oid) return res.status(400).json({ error: 'bad-id' });
1199
+
1200
+ const o = await dbLayer.getOuting(oid);
1201
+ if (!o) return res.status(404).json({ error: 'not-found' });
1202
+
1203
+ const list = normalizeUidList(o.participants);
1204
+ const sUid = String(uid);
1205
+ const next = list.filter((x) => String(x) !== sUid);
1206
+ o.participants = JSON.stringify(next);
1207
+ await dbLayer.saveOuting(o);
1208
+
1209
+ realtime.emitCalendarUpdated({ kind: 'outing', action: 'participants', oid });
1210
+ return res.json({ ok: true, participants: next, participantsUsernames: await usernamesByUids(next) });
1211
+ };
1212
+
1166
1213
  api.createOuting = async function (req, res) {
1167
1214
  const settings = await getSettings();
1168
1215
  if (!req.uid) return res.status(401).json({ error: 'not-logged-in' });
package/library.js CHANGED
@@ -84,6 +84,7 @@ Plugin.init = async function (params) {
84
84
  router.delete('/api/v3/plugins/calendar-onekite/special-events/:eid', ...publicExpose, api.deleteSpecialEvent);
85
85
  // Participants (self-join) for special events
86
86
  router.post('/api/v3/plugins/calendar-onekite/special-events/:eid/participants', ...publicExpose, api.joinSpecialEvent);
87
+ router.delete('/api/v3/plugins/calendar-onekite/special-events/:eid/participants', ...publicExpose, api.leaveSpecialEvent);
87
88
 
88
89
  // Outings (prévisions de sortie)
89
90
  router.post('/api/v3/plugins/calendar-onekite/outings', ...publicExpose, api.createOuting);
@@ -91,6 +92,7 @@ Plugin.init = async function (params) {
91
92
  router.delete('/api/v3/plugins/calendar-onekite/outings/:oid', ...publicExpose, api.deleteOuting);
92
93
  // Participants (self-join) for outings
93
94
  router.post('/api/v3/plugins/calendar-onekite/outings/:oid/participants', ...publicExpose, api.joinOuting);
95
+ router.delete('/api/v3/plugins/calendar-onekite/outings/:oid/participants', ...publicExpose, api.leaveOuting);
94
96
 
95
97
  // Calendar export (ICS)
96
98
  // Note: reservations are protected by a signature in the querystring.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-onekite-calendar",
3
- "version": "2.0.56",
3
+ "version": "2.0.58",
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.56"
42
+ "version": "2.0.58"
43
43
  }
package/public/client.js CHANGED
@@ -1675,10 +1675,14 @@ function toDatetimeLocalValue(date) {
1675
1675
  const hasCoords = Number.isFinite(lat) && Number.isFinite(lon);
1676
1676
  const notes = String(p.notes || '').trim();
1677
1677
  const participants = Array.isArray(p.participantsUsernames) ? p.participantsUsernames : [];
1678
+ const eidPlain = escapeHtml(String(p.eid || '').replace(/^special:/, ''));
1678
1679
  const joinBtn = (p.canJoin && !p.isParticipant)
1679
- ? `<a href="#" class="onekite-join-special ms-2" data-eid="${escapeHtml(String(p.eid || '').replace(/^special:/, ''))}" title="S'ajouter"><i class="fa fa-plus"></i></a>`
1680
+ ? `<a href="#" class="onekite-join-special ms-2" data-eid="${eidPlain}" title="S'ajouter"><i class="fa fa-plus"></i></a>`
1680
1681
  : '';
1681
- const participantsHtml = `<div class="mb-2" id="onekite-participants-special"><strong>Participants</strong>${joinBtn}<br>` +
1682
+ const leaveBtn = (p.canJoin && p.isParticipant)
1683
+ ? `<a href="#" class="onekite-leave-special ms-2" data-eid="${eidPlain}" title="Se retirer"><i class="fa fa-minus"></i></a>`
1684
+ : '';
1685
+ const participantsHtml = `<div class="mb-2" id="onekite-participants-special"><strong>Participants</strong>${joinBtn}${leaveBtn}<br>` +
1682
1686
  (participants.length
1683
1687
  ? participants.map((name) => {
1684
1688
  const u = String(name || '').trim();
@@ -1755,12 +1759,38 @@ function toDatetimeLocalValue(date) {
1755
1759
  return `<a class="onekite-user-link me-2" href="${window.location.origin}/user/${encodeURIComponent(u)}">${escapeHtml(u)}</a>`;
1756
1760
  }).filter(Boolean).join('')
1757
1761
  : `<span class="text-muted">Aucun</span>`;
1758
- box.html(`<strong>Participants</strong><br>${links}`);
1762
+ box.html(`<strong>Participants</strong><a href="#" class="onekite-leave-special ms-2" data-eid="${escapeHtml(eid)}" title="Se retirer"><i class="fa fa-minus"></i></a><br>${links}`);
1759
1763
  }
1760
1764
  } catch (e3) {
1761
1765
  showAlert('error', "Impossible de s'ajouter.");
1762
1766
  }
1763
1767
  });
1768
+
1769
+ dlg.find('.onekite-leave-special').off('click').on('click', async (e2) => {
1770
+ e2.preventDefault();
1771
+ const btn = e2.currentTarget;
1772
+ if (!btn) return;
1773
+ try {
1774
+ btn.classList.add('disabled');
1775
+ const eid = String(btn.getAttribute('data-eid') || '').trim();
1776
+ if (!eid) return;
1777
+ const r = await fetchJson(`/api/v3/plugins/calendar-onekite/special-events/${encodeURIComponent(eid)}/participants`, { method: 'DELETE' });
1778
+ const names = Array.isArray(r && r.participantsUsernames) ? r.participantsUsernames : [];
1779
+ const box = dlg.find('#onekite-participants-special');
1780
+ if (box && box.length) {
1781
+ const links = names.length
1782
+ ? names.map((name) => {
1783
+ const u = String(name || '').trim();
1784
+ if (!u) return '';
1785
+ return `<a class="onekite-user-link me-2" href="${window.location.origin}/user/${encodeURIComponent(u)}">${escapeHtml(u)}</a>`;
1786
+ }).filter(Boolean).join('')
1787
+ : `<span class="text-muted">Aucun</span>`;
1788
+ box.html(`<strong>Participants</strong><a href="#" class="onekite-join-special ms-2" data-eid="${escapeHtml(eid)}" title="S'ajouter"><i class="fa fa-plus"></i></a><br>${links}`);
1789
+ }
1790
+ } catch (e3) {
1791
+ showAlert('error', "Impossible de se retirer.");
1792
+ }
1793
+ });
1764
1794
  });
1765
1795
  } catch (e) {}
1766
1796
  try {
@@ -1783,10 +1813,14 @@ function toDatetimeLocalValue(date) {
1783
1813
  const hasCoords = Number.isFinite(lat) && Number.isFinite(lon);
1784
1814
  const notes = String(p.notes || '').trim();
1785
1815
  const participants = Array.isArray(p.participantsUsernames) ? p.participantsUsernames : [];
1816
+ const oidPlain = escapeHtml(String(p.oid || '').replace(/^outing:/, ''));
1786
1817
  const joinBtn = (p.canJoin && !p.isParticipant)
1787
- ? `<a href="#" class="onekite-join-outing ms-2" data-oid="${escapeHtml(String(p.oid || '').replace(/^outing:/, ''))}" title="S'ajouter"><i class="fa fa-plus"></i></a>`
1818
+ ? `<a href="#" class="onekite-join-outing ms-2" data-oid="${oidPlain}" title="S'ajouter"><i class="fa fa-plus"></i></a>`
1788
1819
  : '';
1789
- const participantsHtml = `<div class="mb-2" id="onekite-participants-outing"><strong>Participants</strong>${joinBtn}<br>` +
1820
+ const leaveBtn = (p.canJoin && p.isParticipant)
1821
+ ? `<a href="#" class="onekite-leave-outing ms-2" data-oid="${oidPlain}" title="Se retirer"><i class="fa fa-minus"></i></a>`
1822
+ : '';
1823
+ const participantsHtml = `<div class="mb-2" id="onekite-participants-outing"><strong>Participants</strong>${joinBtn}${leaveBtn}<br>` +
1790
1824
  (participants.length
1791
1825
  ? participants.map((name) => {
1792
1826
  const u = String(name || '').trim();
@@ -1863,12 +1897,38 @@ function toDatetimeLocalValue(date) {
1863
1897
  return `<a class="onekite-user-link me-2" href="${window.location.origin}/user/${encodeURIComponent(u)}">${escapeHtml(u)}</a>`;
1864
1898
  }).filter(Boolean).join('')
1865
1899
  : `<span class="text-muted">Aucun</span>`;
1866
- box.html(`<strong>Participants</strong><br>${links}`);
1900
+ box.html(`<strong>Participants</strong><a href="#" class="onekite-leave-outing ms-2" data-oid="${escapeHtml(oid)}" title="Se retirer"><i class="fa fa-minus"></i></a><br>${links}`);
1867
1901
  }
1868
1902
  } catch (e3) {
1869
1903
  showAlert('error', "Impossible de s'ajouter.");
1870
1904
  }
1871
1905
  });
1906
+
1907
+ dlg.find('.onekite-leave-outing').off('click').on('click', async (e2) => {
1908
+ e2.preventDefault();
1909
+ const btn = e2.currentTarget;
1910
+ if (!btn) return;
1911
+ try {
1912
+ btn.classList.add('disabled');
1913
+ const oid = String(btn.getAttribute('data-oid') || '').trim();
1914
+ if (!oid) return;
1915
+ const r = await fetchJson(`/api/v3/plugins/calendar-onekite/outings/${encodeURIComponent(oid)}/participants`, { method: 'DELETE' });
1916
+ const names = Array.isArray(r && r.participantsUsernames) ? r.participantsUsernames : [];
1917
+ const box = dlg.find('#onekite-participants-outing');
1918
+ if (box && box.length) {
1919
+ const links = names.length
1920
+ ? names.map((name) => {
1921
+ const u = String(name || '').trim();
1922
+ if (!u) return '';
1923
+ return `<a class="onekite-user-link me-2" href="${window.location.origin}/user/${encodeURIComponent(u)}">${escapeHtml(u)}</a>`;
1924
+ }).filter(Boolean).join('')
1925
+ : `<span class="text-muted">Aucun</span>`;
1926
+ box.html(`<strong>Participants</strong><a href="#" class="onekite-join-outing ms-2" data-oid="${escapeHtml(oid)}" title="S'ajouter"><i class="fa fa-plus"></i></a><br>${links}`);
1927
+ }
1928
+ } catch (e3) {
1929
+ showAlert('error', "Impossible de se retirer.");
1930
+ }
1931
+ });
1872
1932
  });
1873
1933
  } catch (e) {}
1874
1934
  try {