nodebb-plugin-calendar-onekite 11.1.71 → 11.1.72
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/admin.js +24 -1
- package/lib/helloassoWebhook.js +11 -27
- package/library.js +3 -0
- package/package.json +1 -1
- package/public/admin.js +36 -16
- package/public/client.js +64 -1
- package/templates/admin/plugins/calendar-onekite.tpl +30 -4
package/lib/admin.js
CHANGED
|
@@ -22,7 +22,12 @@ async function sendEmail(template, toEmail, subject, data) {
|
|
|
22
22
|
if (!toEmail) return;
|
|
23
23
|
try {
|
|
24
24
|
if (typeof emailer.sendToEmail === 'function') {
|
|
25
|
-
|
|
25
|
+
if (emailer.sendToEmail.length >= 4) {
|
|
26
|
+
await emailer.sendToEmail(template, toEmail, subject, data);
|
|
27
|
+
} else {
|
|
28
|
+
const dataWithSubject = Object.assign({}, data || {}, subject ? { subject } : {});
|
|
29
|
+
await emailer.sendToEmail(template, toEmail, dataWithSubject);
|
|
30
|
+
}
|
|
26
31
|
return;
|
|
27
32
|
}
|
|
28
33
|
if (typeof emailer.send === 'function') {
|
|
@@ -227,6 +232,24 @@ admin.purgeByYear = async function (req, res) {
|
|
|
227
232
|
res.json({ ok: true, removed: count });
|
|
228
233
|
};
|
|
229
234
|
|
|
235
|
+
admin.purgeSpecialEventsByYear = async function (req, res) {
|
|
236
|
+
const year = (req.body && req.body.year ? String(req.body.year) : '').trim();
|
|
237
|
+
if (!/^\d{4}$/.test(year)) {
|
|
238
|
+
return res.status(400).json({ error: 'invalid-year' });
|
|
239
|
+
}
|
|
240
|
+
const y = parseInt(year, 10);
|
|
241
|
+
const startTs = new Date(Date.UTC(y, 0, 1)).getTime();
|
|
242
|
+
const endTs = new Date(Date.UTC(y + 1, 0, 1)).getTime() - 1;
|
|
243
|
+
|
|
244
|
+
const ids = await dbLayer.listSpecialIdsByStartRange(startTs, endTs, 100000);
|
|
245
|
+
let count = 0;
|
|
246
|
+
for (const eid of ids) {
|
|
247
|
+
await dbLayer.removeSpecialEvent(eid);
|
|
248
|
+
count++;
|
|
249
|
+
}
|
|
250
|
+
return res.json({ ok: true, removed: count });
|
|
251
|
+
};
|
|
252
|
+
|
|
230
253
|
// Debug endpoint to validate HelloAsso connectivity and item loading
|
|
231
254
|
|
|
232
255
|
|
package/lib/helloassoWebhook.js
CHANGED
|
@@ -28,7 +28,15 @@ async function sendEmail(template, toEmail, subject, data) {
|
|
|
28
28
|
const emailer = require.main.require('./src/emailer');
|
|
29
29
|
try {
|
|
30
30
|
if (typeof emailer.sendToEmail === 'function') {
|
|
31
|
-
|
|
31
|
+
// NodeBB versions differ:
|
|
32
|
+
// - sendToEmail(template, email, subject, data)
|
|
33
|
+
// - sendToEmail(template, email, data)
|
|
34
|
+
if (emailer.sendToEmail.length >= 4) {
|
|
35
|
+
await emailer.sendToEmail(template, toEmail, subject, data);
|
|
36
|
+
} else {
|
|
37
|
+
const dataWithSubject = Object.assign({}, data || {}, { subject });
|
|
38
|
+
await emailer.sendToEmail(template, toEmail, dataWithSubject);
|
|
39
|
+
}
|
|
32
40
|
return;
|
|
33
41
|
}
|
|
34
42
|
if (typeof emailer.send === 'function') {
|
|
@@ -37,7 +45,8 @@ async function sendEmail(template, toEmail, subject, data) {
|
|
|
37
45
|
return;
|
|
38
46
|
}
|
|
39
47
|
if (emailer.send.length === 3) {
|
|
40
|
-
|
|
48
|
+
const dataWithSubject = Object.assign({}, data || {}, { subject });
|
|
49
|
+
await emailer.send(template, toEmail, dataWithSubject);
|
|
41
50
|
return;
|
|
42
51
|
}
|
|
43
52
|
await emailer.send(template, toEmail, subject, data);
|
|
@@ -185,31 +194,6 @@ function isConfirmedPayment(payload) {
|
|
|
185
194
|
}
|
|
186
195
|
}
|
|
187
196
|
|
|
188
|
-
async function sendEmail(template, toEmail, subject, data) {
|
|
189
|
-
if (!toEmail) return;
|
|
190
|
-
const emailer = require.main.require('./src/emailer');
|
|
191
|
-
try {
|
|
192
|
-
if (typeof emailer.sendToEmail === 'function') {
|
|
193
|
-
await emailer.sendToEmail(template, toEmail, subject, data);
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
if (typeof emailer.send === 'function') {
|
|
197
|
-
if (emailer.send.length >= 4) {
|
|
198
|
-
await emailer.send(template, toEmail, subject, data);
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
if (emailer.send.length === 3) {
|
|
202
|
-
await emailer.send(template, toEmail, data);
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
await emailer.send(template, toEmail, subject, data);
|
|
206
|
-
}
|
|
207
|
-
} catch (err) {
|
|
208
|
-
// eslint-disable-next-line no-console
|
|
209
|
-
console.warn('[calendar-onekite] Failed to send email (webhook)', { template, toEmail, err: String(err && err.message || err) });
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
197
|
function formatFR(tsOrIso) {
|
|
214
198
|
const d = new Date(tsOrIso);
|
|
215
199
|
const dd = String(d.getDate()).padStart(2, '0');
|
package/library.js
CHANGED
|
@@ -110,6 +110,9 @@ Plugin.init = async function (params) {
|
|
|
110
110
|
router.get(`${base}/accounting`, ...adminMws, admin.getAccounting);
|
|
111
111
|
router.get(`${base}/accounting.csv`, ...adminMws, admin.exportAccountingCsv);
|
|
112
112
|
router.post(`${base}/accounting/purge`, ...adminMws, admin.purgeAccounting);
|
|
113
|
+
|
|
114
|
+
// Purge special events by year
|
|
115
|
+
router.post(`${base}/special-events/purge`, ...adminMws, admin.purgeSpecialEventsByYear);
|
|
113
116
|
});
|
|
114
117
|
|
|
115
118
|
// HelloAsso callback endpoint (hardened)
|
package/package.json
CHANGED
package/public/admin.js
CHANGED
|
@@ -419,6 +419,20 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
419
419
|
});
|
|
420
420
|
}
|
|
421
421
|
|
|
422
|
+
async function purgeSpecialEvents(year) {
|
|
423
|
+
try {
|
|
424
|
+
return await fetchJson('/api/v3/admin/plugins/calendar-onekite/special-events/purge', {
|
|
425
|
+
method: 'POST',
|
|
426
|
+
body: JSON.stringify({ year }),
|
|
427
|
+
});
|
|
428
|
+
} catch (e) {
|
|
429
|
+
return await fetchJson('/api/admin/plugins/calendar-onekite/special-events/purge', {
|
|
430
|
+
method: 'POST',
|
|
431
|
+
body: JSON.stringify({ year }),
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
422
436
|
async function debugHelloAsso() {
|
|
423
437
|
try {
|
|
424
438
|
return await fetchJson('/api/v3/admin/plugins/calendar-onekite/debug');
|
|
@@ -439,22 +453,6 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
439
453
|
const form = document.getElementById('onekite-settings-form');
|
|
440
454
|
if (!form) return;
|
|
441
455
|
|
|
442
|
-
// Inject missing settings fields if the template is older
|
|
443
|
-
function ensureTextInput(name, label, help) {
|
|
444
|
-
if (form.querySelector(`[name="${name}"]`)) return;
|
|
445
|
-
const div = document.createElement('div');
|
|
446
|
-
div.className = 'mb-3';
|
|
447
|
-
div.innerHTML = `
|
|
448
|
-
<label class="form-label">${label}</label>
|
|
449
|
-
<input type="text" class="form-control" name="${name}" placeholder="" />
|
|
450
|
-
${help ? `<div class="form-text">${help}</div>` : ''}
|
|
451
|
-
`;
|
|
452
|
-
form.appendChild(div);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
ensureTextInput('specialCreatorGroups', 'Groupes autorisés à créer des évènements (csv)', 'Ex: groupA,groupB');
|
|
456
|
-
ensureTextInput('specialDeleterGroups', 'Groupes autorisés à supprimer des évènements (csv)', 'Par défaut, si vide : même liste que la création');
|
|
457
|
-
|
|
458
456
|
// Load settings
|
|
459
457
|
try {
|
|
460
458
|
const s = await loadSettings();
|
|
@@ -603,6 +601,28 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
603
601
|
});
|
|
604
602
|
}
|
|
605
603
|
|
|
604
|
+
// Purge special events by year
|
|
605
|
+
const sePurgeBtn = document.getElementById('onekite-se-purge');
|
|
606
|
+
if (sePurgeBtn) {
|
|
607
|
+
sePurgeBtn.addEventListener('click', async () => {
|
|
608
|
+
const yearInput = document.getElementById('onekite-se-purge-year');
|
|
609
|
+
const year = (yearInput ? yearInput.value : '').trim();
|
|
610
|
+
if (!/^\d{4}$/.test(year)) {
|
|
611
|
+
showAlert('error', 'Année invalide (YYYY)');
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
bootbox.confirm(`Purger tous les évènements de ${year} ?`, async (ok) => {
|
|
615
|
+
if (!ok) return;
|
|
616
|
+
try {
|
|
617
|
+
const r = await purgeSpecialEvents(year);
|
|
618
|
+
showAlert('success', `Purge OK (${r.removed || 0} supprimé(s)).`);
|
|
619
|
+
} catch (e) {
|
|
620
|
+
showAlert('error', 'Purge impossible.');
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
|
|
606
626
|
// Debug
|
|
607
627
|
const debugBtn = document.getElementById('onekite-debug-run');
|
|
608
628
|
if (debugBtn) {
|
package/public/client.js
CHANGED
|
@@ -118,6 +118,61 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
118
118
|
});
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
async function openMapViewer(title, address, lat, lon) {
|
|
122
|
+
const mapId = `onekite-map-view-${Date.now()}-${Math.floor(Math.random()*10000)}`;
|
|
123
|
+
const safeAddr = (address || '').trim();
|
|
124
|
+
const html = `
|
|
125
|
+
${safeAddr ? `<div class="mb-2">${escapeHtml(safeAddr)}</div>` : ''}
|
|
126
|
+
<div id="${mapId}" style="height:260px; border:1px solid #ddd; border-radius:6px;"></div>
|
|
127
|
+
`;
|
|
128
|
+
bootbox.dialog({
|
|
129
|
+
title: title || 'Carte',
|
|
130
|
+
message: html,
|
|
131
|
+
buttons: { close: { label: 'Fermer', className: 'btn-secondary' } },
|
|
132
|
+
});
|
|
133
|
+
setTimeout(async () => {
|
|
134
|
+
try {
|
|
135
|
+
const el = document.getElementById(mapId);
|
|
136
|
+
if (!el) return;
|
|
137
|
+
const L = await loadLeaflet();
|
|
138
|
+
const map = L.map(el);
|
|
139
|
+
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(map);
|
|
140
|
+
|
|
141
|
+
async function setAt(lat2, lon2) {
|
|
142
|
+
map.setView([lat2, lon2], 14);
|
|
143
|
+
L.marker([lat2, lon2]).addTo(map);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const hasCoords = Number.isFinite(lat) && Number.isFinite(lon);
|
|
147
|
+
if (hasCoords) {
|
|
148
|
+
await setAt(lat, lon);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (safeAddr) {
|
|
152
|
+
const hit = await geocodeAddress(safeAddr);
|
|
153
|
+
if (hit && hit.lat && hit.lon) {
|
|
154
|
+
await setAt(hit.lat, hit.lon);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
map.setView([46.5, 2.5], 5);
|
|
159
|
+
} catch (e) {
|
|
160
|
+
// ignore leaflet errors
|
|
161
|
+
}
|
|
162
|
+
}, 0);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Click handler for map links in popups
|
|
166
|
+
document.addEventListener('click', (ev) => {
|
|
167
|
+
const a = ev.target && ev.target.closest ? ev.target.closest('a.onekite-map-link') : null;
|
|
168
|
+
if (!a) return;
|
|
169
|
+
ev.preventDefault();
|
|
170
|
+
const addr = a.getAttribute('data-address') || a.textContent || '';
|
|
171
|
+
const lat = parseFloat(a.getAttribute('data-lat'));
|
|
172
|
+
const lon = parseFloat(a.getAttribute('data-lon'));
|
|
173
|
+
openMapViewer('Adresse', addr, lat, lon);
|
|
174
|
+
});
|
|
175
|
+
|
|
121
176
|
function statusLabel(s) {
|
|
122
177
|
const map = {
|
|
123
178
|
pending: 'En attente',
|
|
@@ -524,12 +579,20 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
524
579
|
? `<div class="mb-2"><strong>Créé par</strong><br><a href="${window.location.origin}/user/${encodeURIComponent(username)}">${escapeHtml(username)}</a></div>`
|
|
525
580
|
: '';
|
|
526
581
|
const addr = String(p.pickupAddress || '').trim();
|
|
582
|
+
const lat = Number(p.pickupLat);
|
|
583
|
+
const lon = Number(p.pickupLon);
|
|
584
|
+
const hasCoords = Number.isFinite(lat) && Number.isFinite(lon);
|
|
527
585
|
const notes = String(p.notes || '').trim();
|
|
586
|
+
const addrHtml = addr
|
|
587
|
+
? (hasCoords
|
|
588
|
+
? `<a href="#" class="onekite-map-link" data-address="${escapeHtml(addr)}" data-lat="${escapeHtml(String(lat))}" data-lon="${escapeHtml(String(lon))}">${escapeHtml(addr)}</a>`
|
|
589
|
+
: `${escapeHtml(addr)}`)
|
|
590
|
+
: '';
|
|
528
591
|
const html = `
|
|
529
592
|
<div class="mb-2"><strong>Titre</strong><br>${escapeHtml(p.title || ev.title || '')}</div>
|
|
530
593
|
${userLine}
|
|
531
594
|
<div class="mb-2"><strong>Période</strong><br>${escapeHtml(formatDt(ev.start))} → ${escapeHtml(formatDt(ev.end))}</div>
|
|
532
|
-
${addr ? `<div class="mb-2"><strong>Adresse</strong><br>${
|
|
595
|
+
${addr ? `<div class="mb-2"><strong>Adresse</strong><br>${addrHtml}</div>` : ''}
|
|
533
596
|
${notes ? `<div class="mb-2"><strong>Notes</strong><br>${escapeHtml(notes).replace(/\n/g,'<br>')}</div>` : ''}
|
|
534
597
|
`;
|
|
535
598
|
const canDel = !!(p.canDeleteSpecial || canDeleteSpecial);
|
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
<ul class="nav nav-tabs mt-3" role="tablist">
|
|
8
8
|
<li class="nav-item" role="presentation">
|
|
9
|
-
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#onekite-tab-settings" type="button" role="tab">
|
|
9
|
+
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#onekite-tab-settings" type="button" role="tab">Locations</button>
|
|
10
|
+
</li>
|
|
11
|
+
<li class="nav-item" role="presentation">
|
|
12
|
+
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#onekite-tab-events" type="button" role="tab">Évènements</button>
|
|
10
13
|
</li>
|
|
11
14
|
<li class="nav-item" role="presentation">
|
|
12
15
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#onekite-tab-pending" type="button" role="tab">Demandes en attente</button>
|
|
@@ -20,13 +23,13 @@
|
|
|
20
23
|
</ul>
|
|
21
24
|
|
|
22
25
|
<div class="tab-content pt-3">
|
|
26
|
+
<form id="onekite-settings-form" class="mt-1">
|
|
23
27
|
<div class="tab-pane fade show active" id="onekite-tab-settings" role="tabpanel">
|
|
24
|
-
<form id="onekite-settings-form" class="mt-1">
|
|
25
28
|
<h4>Groupes</h4>
|
|
26
29
|
<div class="mb-3">
|
|
27
30
|
<label class="form-label">Groupes autorisés à créer une demande (csv)</label>
|
|
28
31
|
<input class="form-control" name="creatorGroups" placeholder="ex: registered-users,membres">
|
|
29
|
-
<div class="form-text">
|
|
32
|
+
<div class="form-text">Le groupe <code>onekite-ffvl-YYYY</code> est automatiquement ajouté au début. Tu peux ajouter d'autres groupes à la suite.</div>
|
|
30
33
|
</div>
|
|
31
34
|
|
|
32
35
|
<div class="mb-3">
|
|
@@ -84,10 +87,33 @@
|
|
|
84
87
|
<label class="form-label">Form Slug</label>
|
|
85
88
|
<input class="form-control" name="helloassoFormSlug">
|
|
86
89
|
</div>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div class="tab-pane fade" id="onekite-tab-events" role="tabpanel">
|
|
93
|
+
<h4>Évènements (autre couleur)</h4>
|
|
94
|
+
<div class="form-text mb-3">Permet de créer des évènements horaires (début/fin) avec adresse (Leaflet) et notes.</div>
|
|
95
|
+
|
|
96
|
+
<div class="mb-3">
|
|
97
|
+
<label class="form-label">Groupes autorisés à créer des évènements (csv)</label>
|
|
98
|
+
<input class="form-control" name="specialCreatorGroups" placeholder="ex: staff,instructors">
|
|
99
|
+
</div>
|
|
87
100
|
|
|
88
|
-
|
|
101
|
+
<div class="mb-3">
|
|
102
|
+
<label class="form-label">Groupes autorisés à supprimer des évènements (csv)</label>
|
|
103
|
+
<input class="form-control" name="specialDeleterGroups" placeholder="Si vide: même liste que la création">
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<hr class="my-4" />
|
|
107
|
+
<h4>Purge des évènements</h4>
|
|
108
|
+
<div class="d-flex gap-2 align-items-center">
|
|
109
|
+
<input class="form-control" style="max-width: 160px;" id="onekite-se-purge-year" placeholder="YYYY">
|
|
110
|
+
<button type="button" class="btn btn-outline-danger" id="onekite-se-purge">Purger</button>
|
|
111
|
+
</div>
|
|
112
|
+
<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>
|
|
89
113
|
</div>
|
|
90
114
|
|
|
115
|
+
</form>
|
|
116
|
+
|
|
91
117
|
<div class="tab-pane fade" id="onekite-tab-pending" role="tabpanel">
|
|
92
118
|
<h4>Demandes en attente</h4>
|
|
93
119
|
<div id="onekite-pending" class="list-group"></div>
|