nodebb-plugin-equipment-calendar 9.1.3 → 9.1.4

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/library.js CHANGED
@@ -1,9 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const nconf = require.main.require('nconf');
4
3
  const meta = require.main.require('./src/meta');
5
4
  const db = require.main.require('./src/database');
6
- const user = require.main.require('./src/user');
7
5
  const groups = require.main.require('./src/groups');
8
6
  const winston = require.main.require('winston');
9
7
 
@@ -35,16 +33,13 @@ async function getSettings() {
35
33
  async function isUserInAnyGroups(uid, groupCsv) {
36
34
  if (!uid || !groupCsv) return false;
37
35
  const names = String(groupCsv).split(',').map(s => s.trim()).filter(Boolean);
38
- if (!names.length) return false;
39
36
  for (const name of names) {
40
- const inGroup = await groups.isMember(uid, name);
41
- if (inGroup) return true;
37
+ if (await groups.isMember(uid, name)) return true;
42
38
  }
43
39
  return false;
44
40
  }
45
41
 
46
- function rid() {
47
- // UUID v4 (no dependency)
42
+ function genRid() {
48
43
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
49
44
  const r = Math.random() * 16 | 0;
50
45
  const v = c === 'x' ? r : (r & 0x3 | 0x8);
@@ -60,14 +55,13 @@ async function saveReservation(r) {
60
55
  await db.sortedSetAdd(reservationsZset, r.createdAt || Date.now(), r.rid);
61
56
  }
62
57
 
63
- async function listReservations(limit=500) {
58
+ async function listReservations(limit=1000) {
64
59
  const rids = await db.getSortedSetRevRange(reservationsZset, 0, limit - 1);
65
60
  const objs = await Promise.all(rids.map(id => db.getObject(reservationKey(id))));
66
61
  return objs.filter(Boolean);
67
62
  }
68
63
 
69
64
  function toEvent(r) {
70
- // FullCalendar allDay uses exclusive end
71
65
  const start = String(r.startIso).slice(0,10);
72
66
  const end = new Date(String(r.endIso).slice(0,10) + 'T00:00:00Z');
73
67
  end.setUTCDate(end.getUTCDate() + 1);
@@ -75,42 +69,27 @@ function toEvent(r) {
75
69
 
76
70
  const status = r.status || 'pending';
77
71
  const title = status === 'paid' ? '[PAID] Reservation' : (status === 'approved' ? '[APPROVED] Reservation' : '[PENDING] Reservation');
78
-
79
- return {
80
- id: r.rid,
81
- title,
82
- start,
83
- end: endExcl,
84
- allDay: true,
85
- };
72
+ return { id: r.rid, title, start, end: endExcl, allDay: true };
86
73
  }
87
74
 
88
75
  plugin.init = async function (params) {
89
76
  const { router, middleware } = params;
90
77
 
91
- // Admin page
92
78
  router.get('/admin/plugins/equipment-calendar', middleware.admin.buildHeader, async (req, res) => {
93
79
  res.render('admin/plugins/equipment-calendar', {});
94
80
  });
95
- router.get('/api/admin/plugins/equipment-calendar', async (req, res) => {
96
- res.json({});
97
- });
81
+ router.get('/api/admin/plugins/equipment-calendar', async (req, res) => res.json({}));
98
82
 
99
- // Calendar page
100
83
  router.get('/equipment/calendar', middleware.buildHeader, async (req, res) => {
101
84
  res.render('equipment-calendar/calendar', {});
102
85
  });
103
- router.get('/api/equipment/calendar', async (req, res) => {
104
- res.json({});
105
- });
86
+ router.get('/api/equipment/calendar', async (req, res) => res.json({}));
106
87
 
107
- // API: events
108
88
  router.get('/api/plugins/equipment-calendar/events', async (req, res) => {
109
89
  const list = await listReservations(1000);
110
90
  res.json({ events: list.map(toEvent) });
111
91
  });
112
92
 
113
- // API: create reservation
114
93
  router.post('/api/plugins/equipment-calendar/reservations', middleware.applyCSRF, async (req, res) => {
115
94
  const uid = req.uid;
116
95
  if (!uid) return res.status(403).json({ message: 'forbidden' });
@@ -125,36 +104,17 @@ plugin.init = async function (params) {
125
104
  if (!startIso || !endIso) return res.status(400).json({ message: 'dates required' });
126
105
  if (!itemIds.length) return res.status(400).json({ message: 'items required' });
127
106
 
128
- const r = {
129
- rid: rid(),
130
- uid,
131
- startIso,
132
- endIso,
133
- itemIds: itemIds.map(String),
134
- status: 'pending',
135
- createdAt: Date.now(),
136
- };
107
+ const r = { rid: genRid(), uid, startIso, endIso, itemIds: itemIds.map(String), status: 'pending', createdAt: Date.now() };
137
108
  await saveReservation(r);
138
109
  res.json({ ok: true, rid: r.rid });
139
110
  });
140
111
 
141
- winston.info('[equipment-calendar] loaded (official4x skeleton)');
112
+ winston.info('[equipment-calendar] loaded (official4x standard bundle)');
142
113
  };
143
114
 
144
115
  plugin.addAdminMenu = async function (header) {
145
- header.plugins.push({
146
- route: '/plugins/equipment-calendar',
147
- icon: 'fa-calendar',
148
- name: 'Equipment Calendar',
149
- });
116
+ header.plugins.push({ route: '/plugins/equipment-calendar', icon: 'fa-calendar', name: 'Equipment Calendar' });
150
117
  return header;
151
118
  };
152
119
 
153
- // Optional: allow /calendar -> /equipment/calendar without hard redirect
154
- plugin.addRouteAliases = async function (hookData) {
155
- // hookData: { router, middleware, controllers } - but for filter:router.page we can inject page routes elsewhere;
156
- // Keep empty to avoid breaking installs. Alias handled by theme route config if needed.
157
- return hookData;
158
- };
159
-
160
120
  module.exports = plugin;
package/package.json CHANGED
@@ -1,22 +1,11 @@
1
1
  {
2
2
  "name": "nodebb-plugin-equipment-calendar",
3
- "version": "9.1.3",
4
- "description": "Equipment reservation calendar (NodeBB 4.x compatible, official settings pattern)",
3
+ "version": "9.1.4",
4
+ "description": "Equipment reservation calendar (NodeBB 4.x compatible, official ACP settings, FullCalendar standard bundle)",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
7
- "keywords": [
8
- "nodebb",
9
- "plugin",
10
- "calendar",
11
- "reservations",
12
- "fullcalendar"
13
- ],
14
7
  "dependencies": {
15
- "@fullcalendar/core": "^6.1.11",
16
- "@fullcalendar/daygrid": "^6.1.11",
17
- "@fullcalendar/interaction": "^6.1.11",
18
- "@fullcalendar/list": "^6.1.11",
19
- "@fullcalendar/timegrid": "^6.1.11"
8
+ "fullcalendar": "^6.1.20"
20
9
  },
21
10
  "scripts": {
22
11
  "postinstall": "node ./scripts/postinstall.js"
package/plugin.json CHANGED
@@ -1,8 +1,7 @@
1
1
  {
2
2
  "id": "nodebb-plugin-equipment-calendar",
3
3
  "name": "Equipment Calendar",
4
- "description": "Equipment reservation calendar with approvals and HelloAsso integration (skeleton).",
5
- "url": "https://github.com/your-org/nodebb-plugin-equipment-calendar",
4
+ "description": "Equipment reservation calendar (NodeBB 4.x)",
6
5
  "library": "./library.js",
7
6
  "hooks": [
8
7
  {
@@ -12,10 +11,6 @@
12
11
  {
13
12
  "hook": "filter:admin.header.build",
14
13
  "method": "addAdminMenu"
15
- },
16
- {
17
- "hook": "filter:router.page",
18
- "method": "addRouteAliases"
19
14
  }
20
15
  ],
21
16
  "acpScripts": [
package/public/js/acp.js CHANGED
@@ -5,28 +5,32 @@ define('admin/plugins/equipment-calendar', ['settings', 'alerts'], function (Set
5
5
 
6
6
  function onPage() {
7
7
  const url = (ajaxify && ajaxify.data && ajaxify.data.url) ? ajaxify.data.url : (window.location.pathname || '');
8
- if (!url.startsWith('admin/plugins/equipment-calendar')) {
9
- return;
10
- }
8
+ if (!url.startsWith('admin/plugins/equipment-calendar')) return;
9
+
11
10
  const container = $('.equipment-calendar-settings');
12
11
  if (!container.length) return;
13
12
 
14
- // NodeBB 4.x settings module supports sync/persist (official pattern)
15
- if (typeof Settings.sync === 'function') {
16
- Settings.sync('equipmentCalendar', container);
17
- } else {
18
- Settings.load('equipmentCalendar', container);
19
- }
13
+ try {
14
+ if (typeof Settings.sync === 'function') {
15
+ Settings.sync('equipmentCalendar', container);
16
+ } else {
17
+ Settings.load('equipmentCalendar', container);
18
+ }
19
+ } catch (e) {}
20
20
 
21
21
  $('#save').off('click.ec').on('click.ec', function (e) {
22
22
  e.preventDefault();
23
23
  const done = function () {
24
24
  alerts.success('[[admin/settings:settings-saved]]');
25
25
  };
26
- if (typeof Settings.persist === 'function') {
27
- Settings.persist('equipmentCalendar', container, done, true);
28
- } else {
29
- Settings.save('equipmentCalendar', container, done);
26
+ try {
27
+ if (typeof Settings.persist === 'function') {
28
+ Settings.persist('equipmentCalendar', container, done, true);
29
+ } else {
30
+ Settings.save('equipmentCalendar', container, done);
31
+ }
32
+ } catch (err) {
33
+ alerts.error(err && err.message ? err.message : 'Erreur');
30
34
  }
31
35
  });
32
36
  }
@@ -5,12 +5,10 @@ define('equipment-calendar/client', ['api', 'alerts'], function (api, alerts) {
5
5
 
6
6
  function pageMatch() {
7
7
  const url = (ajaxify && ajaxify.data && ajaxify.data.url) ? ajaxify.data.url : '';
8
- return url === 'equipment/calendar' || url === 'calendar';
8
+ return url === 'equipment/calendar';
9
9
  }
10
10
 
11
- function isoDate(d) {
12
- return String(d || '').slice(0, 10);
13
- }
11
+ function isoDate(s) { return String(s || '').slice(0, 10); }
14
12
 
15
13
  async function loadEvents() {
16
14
  const res = await api.get('/plugins/equipment-calendar/events', {});
@@ -21,31 +19,27 @@ define('equipment-calendar/client', ['api', 'alerts'], function (api, alerts) {
21
19
  $('#ec-start').val(startIso);
22
20
  $('#ec-end').val(endIso);
23
21
  const modalEl = document.getElementById('ecModal');
24
- const modal = window.bootstrap ? window.bootstrap.Modal.getOrCreateInstance(modalEl) : null;
25
- if (modal) modal.show();
22
+ if (!modalEl || !window.bootstrap) return;
23
+ window.bootstrap.Modal.getOrCreateInstance(modalEl).show();
26
24
  }
27
25
 
28
26
  async function createReservation(payload) {
29
- const res = await api.post('/plugins/equipment-calendar/reservations', payload);
30
- return res;
27
+ return api.post('/plugins/equipment-calendar/reservations', payload);
31
28
  }
32
29
 
33
30
  async function initCalendar() {
34
31
  const el = document.getElementById('ec-calendar');
35
- if (!el || !window.FullCalendar) return;
32
+ if (!el || !window.FullCalendar || !window.FullCalendar.Calendar) return;
36
33
 
37
34
  const events = await loadEvents();
38
35
 
39
- const calendar = new FullCalendar.Calendar(el, {
40
- plugins: [ FullCalendarDayGrid, FullCalendarTimeGrid, FullCalendarList, FullCalendarInteraction ],
36
+ const calendar = new window.FullCalendar.Calendar(el, {
41
37
  initialView: 'dayGridMonth',
42
38
  headerToolbar: { left: 'prev,next today', center: 'title', right: 'dayGridMonth,timeGridWeek,listWeek' },
43
39
  selectable: true,
44
40
  selectMirror: true,
45
- eventTimeFormat: { hour: '2-digit', minute: '2-digit', hour12: false },
46
- allDaySlot: true,
41
+ displayEventTime: false,
47
42
  select: function (info) {
48
- // all-day selection end is exclusive -> store inclusive end
49
43
  const start = isoDate(info.startStr);
50
44
  const endExcl = isoDate(info.endStr);
51
45
  let end = endExcl;
@@ -61,7 +55,7 @@ define('equipment-calendar/client', ['api', 'alerts'], function (api, alerts) {
61
55
  const d = isoDate(info.dateStr);
62
56
  openModal(d, d);
63
57
  },
64
- events: events,
58
+ events,
65
59
  });
66
60
 
67
61
  calendar.render();
@@ -77,8 +71,7 @@ define('equipment-calendar/client', ['api', 'alerts'], function (api, alerts) {
77
71
  await createReservation({ startIso, endIso, itemIds, _csrf: csrf });
78
72
  alerts.success('Demande envoyée');
79
73
  const modalEl = document.getElementById('ecModal');
80
- const modal = window.bootstrap ? window.bootstrap.Modal.getOrCreateInstance(modalEl) : null;
81
- if (modal) modal.hide();
74
+ if (modalEl && window.bootstrap) window.bootstrap.Modal.getOrCreateInstance(modalEl).hide();
82
75
 
83
76
  const newEvents = await loadEvents();
84
77
  calendar.removeAllEvents();
@@ -89,10 +82,7 @@ define('equipment-calendar/client', ['api', 'alerts'], function (api, alerts) {
89
82
  });
90
83
  }
91
84
 
92
- function onEnd() {
93
- if (!pageMatch()) return;
94
- initCalendar();
95
- }
85
+ function onEnd() { if (pageMatch()) initCalendar(); }
96
86
 
97
87
  Client.init = function () {
98
88
  $(window).off('action:ajaxify.end.ecCal').on('action:ajaxify.end.ecCal', onEnd);
@@ -14,24 +14,12 @@ function exists(p) { try { fs.accessSync(p); return true; } catch { return false
14
14
  const outDir = path.join(root, 'public', 'vendor', 'fullcalendar');
15
15
  fs.mkdirSync(outDir, { recursive: true });
16
16
 
17
- const candidates = [
18
- // FullCalendar v6 provides "index.global.min.js" per package
19
- { pkg: '@fullcalendar/core', js: 'index.global.min.js', css: 'index.global.min.css' },
20
- { pkg: '@fullcalendar/daygrid', js: 'index.global.min.js', css: 'index.global.min.css' },
21
- { pkg: '@fullcalendar/timegrid', js: 'index.global.min.js', css: 'index.global.min.css' },
22
- { pkg: '@fullcalendar/list', js: 'index.global.min.js', css: 'index.global.min.css' },
23
- { pkg: '@fullcalendar/interaction', js: 'index.global.min.js', css: 'index.global.min.css' },
24
- ];
17
+ const js = path.join(root, 'node_modules', 'fullcalendar', 'index.global.min.js');
18
+ const css = path.join(root, 'node_modules', 'fullcalendar', 'index.global.min.css');
25
19
 
26
20
  const copied = [];
27
- candidates.forEach((c) => {
28
- const base = path.join(root, 'node_modules', c.pkg);
29
- const js = path.join(base, c.js);
30
- const css = path.join(base, c.css);
31
- if (exists(js)) { copyFile(js, path.join(outDir, `${c.pkg.replace('@fullcalendar/','')}.${c.js}`)); copied.push(js); }
32
- if (exists(css)) { copyFile(css, path.join(outDir, `${c.pkg.replace('@fullcalendar/','')}.${c.css}`)); copied.push(css); }
33
- });
21
+ if (exists(js)) { copyFile(js, path.join(outDir, 'index.global.min.js')); copied.push(js); }
22
+ if (exists(css)) { copyFile(css, path.join(outDir, 'index.global.min.css')); copied.push(css); }
34
23
 
35
- // Also write a small manifest for debugging
36
24
  fs.writeFileSync(path.join(outDir, 'manifest.json'), JSON.stringify({ copied }, null, 2), 'utf-8');
37
25
  })();
@@ -52,7 +52,7 @@
52
52
  </div>
53
53
 
54
54
  <div class="card card-body mb-3">
55
- <h5 class="mb-3">Réservation</h5>
55
+ <h5 class="mb-3">Calendrier</h5>
56
56
  <div class="row g-3">
57
57
  <div class="col-md-6">
58
58
  <label class="form-label">Timeout paiement (minutes)</label>
@@ -38,16 +38,8 @@
38
38
  </div>
39
39
  </div>
40
40
 
41
- <link rel="stylesheet" href="{config.relative_path}/plugins/nodebb-plugin-equipment-calendar/vendor/fullcalendar/core.index.global.min.css">
42
- <link rel="stylesheet" href="{config.relative_path}/plugins/nodebb-plugin-equipment-calendar/vendor/fullcalendar/daygrid.index.global.min.css">
43
- <link rel="stylesheet" href="{config.relative_path}/plugins/nodebb-plugin-equipment-calendar/vendor/fullcalendar/timegrid.index.global.min.css">
44
- <link rel="stylesheet" href="{config.relative_path}/plugins/nodebb-plugin-equipment-calendar/vendor/fullcalendar/list.index.global.min.css">
45
-
46
- <script src="{config.relative_path}/plugins/nodebb-plugin-equipment-calendar/vendor/fullcalendar/core.index.global.min.js"></script>
47
- <script src="{config.relative_path}/plugins/nodebb-plugin-equipment-calendar/vendor/fullcalendar/interaction.index.global.min.js"></script>
48
- <script src="{config.relative_path}/plugins/nodebb-plugin-equipment-calendar/vendor/fullcalendar/daygrid.index.global.min.js"></script>
49
- <script src="{config.relative_path}/plugins/nodebb-plugin-equipment-calendar/vendor/fullcalendar/timegrid.index.global.min.js"></script>
50
- <script src="{config.relative_path}/plugins/nodebb-plugin-equipment-calendar/vendor/fullcalendar/list.index.global.min.js"></script>
41
+ <link rel="stylesheet" href="{config.relative_path}/plugins/nodebb-plugin-equipment-calendar/vendor/fullcalendar/index.global.min.css">
42
+ <script src="{config.relative_path}/plugins/nodebb-plugin-equipment-calendar/vendor/fullcalendar/index.global.min.js"></script>
51
43
 
52
44
  <script>
53
45
  require(['equipment-calendar/client'], function (mod) { mod.init(); });