nodebb-plugin-calendar-onekite 11.1.84 → 11.1.86
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 +21 -27
- package/lib/scheduler.js +22 -72
- package/library.js +19 -0
- package/package.json +2 -2
- package/plugin.json +5 -1
- package/public/client.js +11 -1
- package/templates/admin/plugins/calendar-onekite.tpl +5 -3
package/lib/api.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
|
|
5
5
|
const meta = require.main.require('./src/meta');
|
|
6
|
+
const emailer = require.main.require('./src/emailer');
|
|
6
7
|
const nconf = require.main.require('nconf');
|
|
7
8
|
const user = require.main.require('./src/user');
|
|
8
9
|
const groups = require.main.require('./src/groups');
|
|
@@ -14,43 +15,30 @@ const helloasso = require('./helloasso');
|
|
|
14
15
|
// We try the common forms. Any failure is logged for debugging.
|
|
15
16
|
async function sendEmail(template, toEmail, subject, data) {
|
|
16
17
|
if (!toEmail) return;
|
|
17
|
-
const emailer = require.main.require('./src/emailer');
|
|
18
18
|
try {
|
|
19
|
-
//
|
|
19
|
+
// NodeBB core signature (historically):
|
|
20
|
+
// Emailer.sendToEmail(template, email, language, params[, callback])
|
|
21
|
+
// Subject is not a positional arg; it must be injected (either by NodeBB itself
|
|
22
|
+
// or via filter:email.modify). We always pass it in params.subject.
|
|
23
|
+
const language = (meta && meta.config && (meta.config.defaultLang || meta.config.defaultLanguage)) || 'fr';
|
|
24
|
+
const params = Object.assign({}, data || {}, subject ? { subject } : {});
|
|
20
25
|
if (typeof emailer.sendToEmail === 'function') {
|
|
21
|
-
|
|
22
|
-
await emailer.sendToEmail(template, toEmail, subject, data);
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
if (emailer.sendToEmail.length === 3) {
|
|
26
|
-
const dataWithSubject = Object.assign({}, data || {}, subject ? { subject } : {});
|
|
27
|
-
await emailer.sendToEmail(template, toEmail, dataWithSubject);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
// Fallback
|
|
31
|
-
await emailer.sendToEmail(template, toEmail, subject, data);
|
|
26
|
+
await emailer.sendToEmail(template, toEmail, language, params);
|
|
32
27
|
return;
|
|
33
28
|
}
|
|
29
|
+
// Fallback for older/unusual builds (rare)
|
|
34
30
|
if (typeof emailer.send === 'function') {
|
|
35
|
-
//
|
|
31
|
+
// Some builds accept (template, email, language, params)
|
|
36
32
|
if (emailer.send.length >= 4) {
|
|
37
|
-
await emailer.send(template, toEmail,
|
|
33
|
+
await emailer.send(template, toEmail, language, params);
|
|
38
34
|
return;
|
|
39
35
|
}
|
|
40
|
-
// Some builds
|
|
41
|
-
|
|
42
|
-
if (emailer.send.length === 3) {
|
|
43
|
-
const dataWithSubject = Object.assign({}, data || {}, subject ? { subject } : {});
|
|
44
|
-
await emailer.send(template, toEmail, dataWithSubject);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
// Fallback: try 4-args anyway
|
|
48
|
-
await emailer.send(template, toEmail, subject, data);
|
|
49
|
-
return;
|
|
36
|
+
// Some builds accept (template, email, params)
|
|
37
|
+
await emailer.send(template, toEmail, params);
|
|
50
38
|
}
|
|
51
39
|
} catch (err) {
|
|
52
40
|
// eslint-disable-next-line no-console
|
|
53
|
-
console.warn('[calendar-onekite] Failed to send email', { template, toEmail, err: String(err
|
|
41
|
+
console.warn('[calendar-onekite] Failed to send email', { template, toEmail, err: String(err) });
|
|
54
42
|
}
|
|
55
43
|
}
|
|
56
44
|
|
|
@@ -195,6 +183,7 @@ async function canDeleteSpecial(uid, settings) {
|
|
|
195
183
|
function eventsFor(resv) {
|
|
196
184
|
const status = resv.status;
|
|
197
185
|
const icons = { pending: '⏳', awaiting_payment: '💳', paid: '✅' };
|
|
186
|
+
const colors = { pending: '#f39c12', awaiting_payment: '#d35400', paid: '#27ae60' };
|
|
198
187
|
const startIsoDate = new Date(parseInt(resv.start, 10)).toISOString().slice(0, 10);
|
|
199
188
|
const endIsoDate = new Date(parseInt(resv.end, 10)).toISOString().slice(0, 10);
|
|
200
189
|
|
|
@@ -211,6 +200,9 @@ function eventsFor(resv) {
|
|
|
211
200
|
// keep id unique per item for FullCalendar, but keep the real rid in extendedProps.rid
|
|
212
201
|
id: `${resv.rid}:${itemId || i}`,
|
|
213
202
|
title: `${icons[status] || ''} ${itemName}`.trim(),
|
|
203
|
+
backgroundColor: colors[status] || '#3498db',
|
|
204
|
+
borderColor: colors[status] || '#3498db',
|
|
205
|
+
textColor: '#ffffff',
|
|
214
206
|
allDay: true,
|
|
215
207
|
start: startIsoDate,
|
|
216
208
|
end: endIsoDate,
|
|
@@ -239,7 +231,9 @@ function eventsForSpecial(ev) {
|
|
|
239
231
|
allDay: false,
|
|
240
232
|
start: startIso,
|
|
241
233
|
end: endIso,
|
|
242
|
-
|
|
234
|
+
backgroundColor: '#8e44ad',
|
|
235
|
+
borderColor: '#8e44ad',
|
|
236
|
+
textColor: '#ffffff',
|
|
243
237
|
extendedProps: {
|
|
244
238
|
type: 'special',
|
|
245
239
|
eid: ev.eid,
|
package/lib/scheduler.js
CHANGED
|
@@ -56,81 +56,31 @@ async function processAwaitingPayment() {
|
|
|
56
56
|
const user = require.main.require('./src/user');
|
|
57
57
|
|
|
58
58
|
async function sendEmail(template, toEmail, subject, data) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (emailer.send.length === 3) {
|
|
71
|
-
await emailer.send(template, toEmail, data);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
await emailer.send(template, toEmail, subject, data);
|
|
75
|
-
}
|
|
76
|
-
} catch (err) {
|
|
77
|
-
// eslint-disable-next-line no-console
|
|
78
|
-
console.warn('[calendar-onekite] Failed to send email (scheduler)', { template, toEmail, err: String(err && err.message || err) });
|
|
59
|
+
if (!toEmail) return;
|
|
60
|
+
try {
|
|
61
|
+
// NodeBB core signature (historically):
|
|
62
|
+
// Emailer.sendToEmail(template, email, language, params[, callback])
|
|
63
|
+
// Subject is not a positional arg; it must be injected (either by NodeBB itself
|
|
64
|
+
// or via filter:email.modify). We always pass it in params.subject.
|
|
65
|
+
const language = (meta && meta.config && (meta.config.defaultLang || meta.config.defaultLanguage)) || 'fr';
|
|
66
|
+
const params = Object.assign({}, data || {}, subject ? { subject } : {});
|
|
67
|
+
if (typeof emailer.sendToEmail === 'function') {
|
|
68
|
+
await emailer.sendToEmail(template, toEmail, language, params);
|
|
69
|
+
return;
|
|
79
70
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const yyyy = d.getFullYear();
|
|
87
|
-
return `${dd}/${mm}/${yyyy}`;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
for (const rid of ids) {
|
|
91
|
-
const r = await dbLayer.getReservation(rid);
|
|
92
|
-
if (!r || r.status !== 'awaiting_payment') continue;
|
|
93
|
-
|
|
94
|
-
const approvedAt = parseInt(r.approvedAt || r.validatedAt || 0, 10) || 0;
|
|
95
|
-
if (!approvedAt) continue;
|
|
96
|
-
|
|
97
|
-
const reminderAt = approvedAt + holdMins * 60 * 1000;
|
|
98
|
-
const expireAt = approvedAt + 2 * holdMins * 60 * 1000;
|
|
99
|
-
|
|
100
|
-
if (!r.reminderSent && now >= reminderAt && now < expireAt) {
|
|
101
|
-
// Send reminder once
|
|
102
|
-
const u = await user.getUserFields(r.uid, ['username', 'email']);
|
|
103
|
-
if (u && u.email) {
|
|
104
|
-
await sendEmail('calendar-onekite_reminder', u.email, 'Location matériel - Rappel', {
|
|
105
|
-
username: u.username,
|
|
106
|
-
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : (r.itemName || '')),
|
|
107
|
-
itemNames: (Array.isArray(r.itemNames) ? r.itemNames : (r.itemName ? [r.itemName] : [])),
|
|
108
|
-
dateRange: `Du ${formatFR(r.start)} au ${formatFR(r.end)}`,
|
|
109
|
-
paymentUrl: r.paymentUrl || '',
|
|
110
|
-
delayMinutes: holdMins,
|
|
111
|
-
pickupLine: r.pickupTime ? (r.adminNote ? `${r.pickupTime} à ${r.adminNote}` : r.pickupTime) : '',
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
r.reminderSent = true;
|
|
115
|
-
r.reminderAt = now;
|
|
116
|
-
await dbLayer.saveReservation(r);
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (now >= expireAt) {
|
|
121
|
-
// Expire: remove reservation so it disappears from calendar and frees items
|
|
122
|
-
const u = await user.getUserFields(r.uid, ['username', 'email']);
|
|
123
|
-
if (u && u.email) {
|
|
124
|
-
await sendEmail('calendar-onekite_expired', u.email, 'Location matériel - Rappel', {
|
|
125
|
-
username: u.username,
|
|
126
|
-
itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : (r.itemName || '')),
|
|
127
|
-
itemNames: (Array.isArray(r.itemNames) ? r.itemNames : (r.itemName ? [r.itemName] : [])),
|
|
128
|
-
dateRange: `Du ${formatFR(r.start)} au ${formatFR(r.end)}`,
|
|
129
|
-
delayMinutes: holdMins,
|
|
130
|
-
});
|
|
71
|
+
// Fallback for older/unusual builds (rare)
|
|
72
|
+
if (typeof emailer.send === 'function') {
|
|
73
|
+
// Some builds accept (template, email, language, params)
|
|
74
|
+
if (emailer.send.length >= 4) {
|
|
75
|
+
await emailer.send(template, toEmail, language, params);
|
|
76
|
+
return;
|
|
131
77
|
}
|
|
132
|
-
|
|
78
|
+
// Some builds accept (template, email, params)
|
|
79
|
+
await emailer.send(template, toEmail, params);
|
|
133
80
|
}
|
|
81
|
+
} catch (err) {
|
|
82
|
+
// eslint-disable-next-line no-console
|
|
83
|
+
console.warn('[calendar-onekite] Failed to send email', { template, toEmail, err: String(err) });
|
|
134
84
|
}
|
|
135
85
|
}
|
|
136
86
|
|
package/library.js
CHANGED
|
@@ -148,4 +148,23 @@ Plugin.addAdminNavigation = async function (header) {
|
|
|
148
148
|
return header;
|
|
149
149
|
};
|
|
150
150
|
|
|
151
|
+
|
|
152
|
+
// Ensure our transactional emails always get a subject.
|
|
153
|
+
// NodeBB's Emailer.sendToEmail signature expects (template, email, language, params),
|
|
154
|
+
// so plugins typically inject/modify the subject via this hook.
|
|
155
|
+
Plugin.emailModify = async function (data) {
|
|
156
|
+
try {
|
|
157
|
+
if (!data || !data.template) return data;
|
|
158
|
+
const tpl = String(data.template);
|
|
159
|
+
if (!tpl.startsWith('calendar-onekite_')) return data;
|
|
160
|
+
|
|
161
|
+
// If the caller provided a subject (we pass it in params.subject), copy it to data.subject.
|
|
162
|
+
const provided = data.params && data.params.subject ? String(data.params.subject) : '';
|
|
163
|
+
if (provided && (!data.subject || !String(data.subject).trim())) {
|
|
164
|
+
data.subject = provided;
|
|
165
|
+
}
|
|
166
|
+
} catch (e) {}
|
|
167
|
+
return data;
|
|
168
|
+
};
|
|
169
|
+
|
|
151
170
|
module.exports = Plugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-calendar-onekite",
|
|
3
|
-
"version": "11.1.
|
|
3
|
+
"version": "11.1.86",
|
|
4
4
|
"description": "FullCalendar-based equipment reservation workflow with admin approval & HelloAsso payment for NodeBB",
|
|
5
5
|
"main": "library.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,4 +8,4 @@
|
|
|
8
8
|
"node": ">=18"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {}
|
|
11
|
-
}
|
|
11
|
+
}
|
package/plugin.json
CHANGED
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
{
|
|
12
12
|
"hook": "filter:admin.header.build",
|
|
13
13
|
"method": "addAdminNavigation"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"hook": "filter:email.modify",
|
|
17
|
+
"method": "emailModify"
|
|
14
18
|
}
|
|
15
19
|
],
|
|
16
20
|
"staticDirs": {
|
|
@@ -27,5 +31,5 @@
|
|
|
27
31
|
"acpScripts": [
|
|
28
32
|
"public/admin.js"
|
|
29
33
|
],
|
|
30
|
-
"version": "1.0.
|
|
34
|
+
"version": "1.0.47"
|
|
31
35
|
}
|
package/public/client.js
CHANGED
|
@@ -332,6 +332,16 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
332
332
|
}
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
+
|
|
336
|
+
function formatDtWithTime(d) {
|
|
337
|
+
try {
|
|
338
|
+
return new Date(d).toLocaleString('fr-FR', { dateStyle: 'short', timeStyle: 'short' });
|
|
339
|
+
} catch (e) {
|
|
340
|
+
return String(d);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
|
|
335
345
|
function toDatetimeLocalValue(date) {
|
|
336
346
|
const d = new Date(date);
|
|
337
347
|
const pad = (n) => String(n).padStart(2, '0');
|
|
@@ -591,7 +601,7 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
591
601
|
const html = `
|
|
592
602
|
<div class="mb-2"><strong>Titre</strong><br>${escapeHtml(p.title || ev.title || '')}</div>
|
|
593
603
|
${userLine}
|
|
594
|
-
<div class="mb-2"><strong>Période</strong><br>${escapeHtml(
|
|
604
|
+
<div class="mb-2"><strong>Période</strong><br>${escapeHtml(formatDtWithTime(ev.start))} → ${escapeHtml(formatDtWithTime(ev.end))}</div>
|
|
595
605
|
${addr ? `<div class="mb-2"><strong>Adresse</strong><br>${addrHtml}</div>` : ''}
|
|
596
606
|
${notes ? `<div class="mb-2"><strong>Notes</strong><br>${escapeHtml(notes).replace(/\n/g,'<br>')}</div>` : ''}
|
|
597
607
|
`;
|
|
@@ -23,9 +23,10 @@
|
|
|
23
23
|
</ul>
|
|
24
24
|
|
|
25
25
|
<div class="tab-content pt-3">
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
<div class="tab-pane fade show active" id="onekite-tab-settings" role="tabpanel">
|
|
28
|
-
|
|
28
|
+
<form id="onekite-settings-form" class="mt-1">
|
|
29
|
+
<h4>Groupes</h4>
|
|
29
30
|
<div class="mb-3">
|
|
30
31
|
<label class="form-label">Groupes autorisés à créer une demande (csv)</label>
|
|
31
32
|
<input class="form-control" name="creatorGroups" placeholder="ex: registered-users,membres">
|
|
@@ -87,6 +88,7 @@
|
|
|
87
88
|
<label class="form-label">Form Slug</label>
|
|
88
89
|
<input class="form-control" name="helloassoFormSlug">
|
|
89
90
|
</div>
|
|
91
|
+
</form>
|
|
90
92
|
</div>
|
|
91
93
|
|
|
92
94
|
<div class="tab-pane fade" id="onekite-tab-events" role="tabpanel">
|
|
@@ -112,7 +114,7 @@
|
|
|
112
114
|
<div class="form-text mt-2">Supprime définitivement tous les évènements dont la date de début est dans l'année sélectionnée.</div>
|
|
113
115
|
</div>
|
|
114
116
|
|
|
115
|
-
|
|
117
|
+
|
|
116
118
|
|
|
117
119
|
<div class="tab-pane fade" id="onekite-tab-pending" role="tabpanel">
|
|
118
120
|
<h4>Demandes en attente</h4>
|