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 +9 -49
- package/package.json +3 -14
- package/plugin.json +1 -6
- package/public/js/acp.js +17 -13
- package/public/js/client.js +11 -21
- package/scripts/postinstall.js +4 -16
- package/templates/admin/plugins/equipment-calendar.tpl +1 -1
- package/templates/equipment-calendar/calendar.tpl +2 -10
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
|
-
|
|
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
|
|
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=
|
|
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
|
|
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.
|
|
4
|
-
"description": "Equipment reservation calendar (NodeBB 4.x compatible, official settings
|
|
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
|
-
"
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
27
|
-
Settings.persist
|
|
28
|
-
|
|
29
|
-
|
|
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
|
}
|
package/public/js/client.js
CHANGED
|
@@ -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'
|
|
8
|
+
return url === 'equipment/calendar';
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function isoDate(
|
|
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
|
-
|
|
25
|
-
|
|
22
|
+
if (!modalEl || !window.bootstrap) return;
|
|
23
|
+
window.bootstrap.Modal.getOrCreateInstance(modalEl).show();
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
async function createReservation(payload) {
|
|
29
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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);
|
package/scripts/postinstall.js
CHANGED
|
@@ -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
|
|
18
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
})();
|
|
@@ -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/
|
|
42
|
-
<
|
|
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(); });
|