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 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
- // Newer NodeBB builds expose sendToEmail
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
- if (emailer.sendToEmail.length >= 4) {
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
- // Common: (template, email, subject, data)
31
+ // Some builds accept (template, email, language, params)
36
32
  if (emailer.send.length >= 4) {
37
- await emailer.send(template, toEmail, subject, data);
33
+ await emailer.send(template, toEmail, language, params);
38
34
  return;
39
35
  }
40
- // Some builds: (template, email, data)
41
- // In that case, subject is expected inside `data.subject`.
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 && err.message || 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
- color: '#8e44ad',
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
- if (!toEmail) return;
60
- try {
61
- if (typeof emailer.sendToEmail === 'function') {
62
- await emailer.sendToEmail(template, toEmail, subject, data);
63
- return;
64
- }
65
- if (typeof emailer.send === 'function') {
66
- if (emailer.send.length >= 4) {
67
- await emailer.send(template, toEmail, subject, data);
68
- return;
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
- function formatFR(ts) {
83
- const d = new Date(ts);
84
- const dd = String(d.getDate()).padStart(2, '0');
85
- const mm = String(d.getMonth() + 1).padStart(2, '0');
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
- await dbLayer.removeReservation(rid);
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.84",
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.46"
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(formatDt(ev.start))} → ${escapeHtml(formatDt(ev.end))}</div>
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
- <form id="onekite-settings-form" class="mt-1">
26
+
27
27
  <div class="tab-pane fade show active" id="onekite-tab-settings" role="tabpanel">
28
- <h4>Groupes</h4>
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
- </form>
117
+
116
118
 
117
119
  <div class="tab-pane fade" id="onekite-tab-pending" role="tabpanel">
118
120
  <h4>Demandes en attente</h4>