nodebb-plugin-onekite-calendar 2.0.67 → 2.0.69
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 +73 -2
- package/library.js +2 -0
- package/package.json +1 -1
- package/public/admin.js +26 -0
- package/public/client.js +139 -14
- package/templates/admin/plugins/calendar-onekite.tpl +6 -4
package/lib/api.js
CHANGED
|
@@ -647,6 +647,7 @@ api.getSpecialEventDetails = async function (req, res) {
|
|
|
647
647
|
const settings = await getSettings();
|
|
648
648
|
const canMod = uid ? await canValidate(uid, settings) : false;
|
|
649
649
|
const canSpecialDelete = uid ? await canDeleteSpecial(uid, settings) : false;
|
|
650
|
+
const canSpecialCreate = uid ? await canCreateSpecial(uid, settings) : false;
|
|
650
651
|
|
|
651
652
|
const eid = String(req.params.eid || '').trim();
|
|
652
653
|
if (!eid) return res.status(400).json({ error: 'missing-eid' });
|
|
@@ -680,6 +681,7 @@ api.getSpecialEventDetails = async function (req, res) {
|
|
|
680
681
|
notes: ev.notes || '',
|
|
681
682
|
participants,
|
|
682
683
|
participantsUsernames: await usernamesByUids(participants),
|
|
684
|
+
canEditSpecial: canSpecialCreate,
|
|
683
685
|
canDeleteSpecial: canSpecialDelete,
|
|
684
686
|
canJoin: uid ? await canJoinSpecial(uid, settings) : false,
|
|
685
687
|
isParticipant: uid ? participants.includes(String(uid)) : false,
|
|
@@ -768,6 +770,7 @@ api.getCapabilities = async function (req, res) {
|
|
|
768
770
|
canDeleteSpecial: false,
|
|
769
771
|
canCreateOuting: false,
|
|
770
772
|
canCreateReservation: false,
|
|
773
|
+
specialEventCategoryCid: 0,
|
|
771
774
|
});
|
|
772
775
|
}
|
|
773
776
|
const [canMod, canSpecialC, canSpecialD, canOuting, canRes] = await Promise.all([
|
|
@@ -783,6 +786,7 @@ api.getCapabilities = async function (req, res) {
|
|
|
783
786
|
canDeleteSpecial: canSpecialD,
|
|
784
787
|
canCreateOuting: canOuting,
|
|
785
788
|
canCreateReservation: canRes,
|
|
789
|
+
specialEventCategoryCid: parseInt(settings && settings.specialEventCategoryId, 10) || 0,
|
|
786
790
|
});
|
|
787
791
|
};
|
|
788
792
|
|
|
@@ -815,6 +819,7 @@ api.createSpecialEvent = async function (req, res) {
|
|
|
815
819
|
const notes = String((req.body && req.body.notes) || '').trim();
|
|
816
820
|
const lat = String((req.body && req.body.lat) || '').trim();
|
|
817
821
|
const lon = String((req.body && req.body.lon) || '').trim();
|
|
822
|
+
const content = String((req.body && req.body.content) || '').trim();
|
|
818
823
|
|
|
819
824
|
const u = await user.getUserFields(req.uid, ['username']);
|
|
820
825
|
const eid = crypto.randomUUID();
|
|
@@ -827,6 +832,7 @@ api.createSpecialEvent = async function (req, res) {
|
|
|
827
832
|
notes,
|
|
828
833
|
lat,
|
|
829
834
|
lon,
|
|
835
|
+
content,
|
|
830
836
|
uid: String(req.uid),
|
|
831
837
|
username: u && u.username ? String(u.username) : '',
|
|
832
838
|
createdAt: String(Date.now()),
|
|
@@ -841,7 +847,8 @@ api.createSpecialEvent = async function (req, res) {
|
|
|
841
847
|
|
|
842
848
|
// Real-time refresh for all viewers
|
|
843
849
|
realtime.emitCalendarUpdated({ kind: 'specialEvent', action: 'created', eid: ev.eid });
|
|
844
|
-
|
|
850
|
+
const categoryCid = parseInt(settings && settings.specialEventCategoryId, 10) || 0;
|
|
851
|
+
res.json({ ok: true, eid, categoryCid });
|
|
845
852
|
};
|
|
846
853
|
|
|
847
854
|
api.deleteSpecialEvent = async function (req, res) {
|
|
@@ -866,6 +873,37 @@ api.deleteSpecialEvent = async function (req, res) {
|
|
|
866
873
|
res.json({ ok: true });
|
|
867
874
|
};
|
|
868
875
|
|
|
876
|
+
api.updateSpecialEvent = async function (req, res) {
|
|
877
|
+
const settings = await getSettings();
|
|
878
|
+
if (!req.uid) return res.status(401).json({ error: 'not-logged-in' });
|
|
879
|
+
const ok = await canCreateSpecial(req.uid, settings);
|
|
880
|
+
if (!ok) return res.status(403).json({ error: 'not-allowed' });
|
|
881
|
+
|
|
882
|
+
const eid = String(req.params.eid || '').replace(/^special:/, '').trim();
|
|
883
|
+
if (!eid) return res.status(400).json({ error: 'bad-id' });
|
|
884
|
+
|
|
885
|
+
const ev = await dbLayer.getSpecialEvent(eid);
|
|
886
|
+
if (!ev) return res.status(404).json({ error: 'not-found' });
|
|
887
|
+
|
|
888
|
+
const startTs = toTs(req.body && req.body.start);
|
|
889
|
+
const endTs = toTs(req.body && req.body.end);
|
|
890
|
+
if (!Number.isFinite(startTs) || !Number.isFinite(endTs) || !(startTs < endTs)) {
|
|
891
|
+
return res.status(400).json({ error: 'bad-dates' });
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
ev.title = String((req.body && req.body.title) || '').trim() || ev.title || 'Évènement';
|
|
895
|
+
ev.start = String(startTs);
|
|
896
|
+
ev.end = String(endTs);
|
|
897
|
+
ev.address = String((req.body && req.body.address) || '').trim();
|
|
898
|
+
ev.lat = String((req.body && req.body.lat) || '').trim();
|
|
899
|
+
ev.lon = String((req.body && req.body.lon) || '').trim();
|
|
900
|
+
ev.notes = String((req.body && req.body.notes) || '').trim();
|
|
901
|
+
|
|
902
|
+
await dbLayer.saveSpecialEvent(ev);
|
|
903
|
+
realtime.emitCalendarUpdated({ kind: 'specialEvent', action: 'updated', eid });
|
|
904
|
+
return res.json({ ok: true });
|
|
905
|
+
};
|
|
906
|
+
|
|
869
907
|
/**
|
|
870
908
|
* Get detailed information about an outing (prévision de sortie).
|
|
871
909
|
*
|
|
@@ -906,6 +944,7 @@ api.getOutingDetails = async function (req, res) {
|
|
|
906
944
|
});
|
|
907
945
|
|
|
908
946
|
const participants = normalizeUidList(o.participants);
|
|
947
|
+
const canEditOuting = uid ? await canRequest(uid, settings, Date.now()) : false;
|
|
909
948
|
const out = {
|
|
910
949
|
oid: o.oid,
|
|
911
950
|
title: o.title || '',
|
|
@@ -917,7 +956,8 @@ api.getOutingDetails = async function (req, res) {
|
|
|
917
956
|
notes: o.notes || '',
|
|
918
957
|
participants,
|
|
919
958
|
participantsUsernames: await usernamesByUids(participants),
|
|
920
|
-
|
|
959
|
+
canEditOuting,
|
|
960
|
+
canJoin: canEditOuting,
|
|
921
961
|
isParticipant: uid ? participants.includes(String(uid)) : false,
|
|
922
962
|
canDeleteOuting: canMod || (uid && String(uid) === String(o.uid)),
|
|
923
963
|
icsUrl: links.icsUrl,
|
|
@@ -1085,6 +1125,37 @@ api.deleteOuting = async function (req, res) {
|
|
|
1085
1125
|
return res.json({ ok: true });
|
|
1086
1126
|
};
|
|
1087
1127
|
|
|
1128
|
+
api.updateOuting = async function (req, res) {
|
|
1129
|
+
const settings = await getSettings();
|
|
1130
|
+
if (!req.uid) return res.status(401).json({ error: 'not-logged-in' });
|
|
1131
|
+
const ok = await canRequest(req.uid, settings, Date.now());
|
|
1132
|
+
if (!ok) return res.status(403).json({ error: 'not-allowed' });
|
|
1133
|
+
|
|
1134
|
+
const oid = String(req.params.oid || '').replace(/^outing:/, '').trim();
|
|
1135
|
+
if (!oid) return res.status(400).json({ error: 'bad-id' });
|
|
1136
|
+
|
|
1137
|
+
const o = await dbLayer.getOuting(oid);
|
|
1138
|
+
if (!o) return res.status(404).json({ error: 'not-found' });
|
|
1139
|
+
|
|
1140
|
+
const startTs = toTs(req.body && req.body.start);
|
|
1141
|
+
const endTs = toTs(req.body && req.body.end);
|
|
1142
|
+
if (!Number.isFinite(startTs) || !Number.isFinite(endTs) || !(startTs < endTs)) {
|
|
1143
|
+
return res.status(400).json({ error: 'bad-dates' });
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
o.title = String((req.body && req.body.title) || '').trim() || o.title || 'Sortie';
|
|
1147
|
+
o.start = String(startTs);
|
|
1148
|
+
o.end = String(endTs);
|
|
1149
|
+
o.address = String((req.body && req.body.address) || '').trim();
|
|
1150
|
+
o.lat = String((req.body && req.body.lat) || '').trim();
|
|
1151
|
+
o.lon = String((req.body && req.body.lon) || '').trim();
|
|
1152
|
+
o.notes = String((req.body && req.body.notes) || '').trim();
|
|
1153
|
+
|
|
1154
|
+
await dbLayer.saveOuting(o);
|
|
1155
|
+
realtime.emitCalendarUpdated({ kind: 'outing', action: 'updated', oid });
|
|
1156
|
+
return res.json({ ok: true });
|
|
1157
|
+
};
|
|
1158
|
+
|
|
1088
1159
|
api.getItems = async function (req, res) {
|
|
1089
1160
|
const settings = await getSettings();
|
|
1090
1161
|
|
package/library.js
CHANGED
|
@@ -82,6 +82,7 @@ Plugin.init = async function (params) {
|
|
|
82
82
|
router.post('/api/v3/plugins/calendar-onekite/special-events', ...publicExpose, api.createSpecialEvent);
|
|
83
83
|
router.get('/api/v3/plugins/calendar-onekite/special-events/:eid', ...publicExpose, api.getSpecialEventDetails);
|
|
84
84
|
router.delete('/api/v3/plugins/calendar-onekite/special-events/:eid', ...publicExpose, api.deleteSpecialEvent);
|
|
85
|
+
router.put('/api/v3/plugins/calendar-onekite/special-events/:eid', ...publicExpose, api.updateSpecialEvent);
|
|
85
86
|
// Participants (self-join) for special events
|
|
86
87
|
router.post('/api/v3/plugins/calendar-onekite/special-events/:eid/participants', ...publicExpose, api.joinSpecialEvent);
|
|
87
88
|
router.delete('/api/v3/plugins/calendar-onekite/special-events/:eid/participants', ...publicExpose, api.leaveSpecialEvent);
|
|
@@ -90,6 +91,7 @@ Plugin.init = async function (params) {
|
|
|
90
91
|
router.post('/api/v3/plugins/calendar-onekite/outings', ...publicExpose, api.createOuting);
|
|
91
92
|
router.get('/api/v3/plugins/calendar-onekite/outings/:oid', ...publicExpose, api.getOutingDetails);
|
|
92
93
|
router.delete('/api/v3/plugins/calendar-onekite/outings/:oid', ...publicExpose, api.deleteOuting);
|
|
94
|
+
router.put('/api/v3/plugins/calendar-onekite/outings/:oid', ...publicExpose, api.updateOuting);
|
|
93
95
|
// Participants (self-join) for outings
|
|
94
96
|
router.post('/api/v3/plugins/calendar-onekite/outings/:oid/participants', ...publicExpose, api.joinOuting);
|
|
95
97
|
router.delete('/api/v3/plugins/calendar-onekite/outings/:oid/participants', ...publicExpose, api.leaveOuting);
|
package/package.json
CHANGED
package/public/admin.js
CHANGED
|
@@ -615,6 +615,32 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
615
615
|
const s = await loadSettings();
|
|
616
616
|
fillForm(form, s || {});
|
|
617
617
|
|
|
618
|
+
// Populate special event category selector
|
|
619
|
+
const catSelect = document.getElementById('onekite-special-category-select');
|
|
620
|
+
if (catSelect) {
|
|
621
|
+
try {
|
|
622
|
+
const cats = await fetchJson('/api/categories');
|
|
623
|
+
const list = cats && (cats.categories || cats.categoryList || cats.data) || [];
|
|
624
|
+
function flatCats(arr) {
|
|
625
|
+
const out = [];
|
|
626
|
+
for (const c of (arr || [])) {
|
|
627
|
+
if (!c || !c.cid) continue;
|
|
628
|
+
out.push(c);
|
|
629
|
+
if (c.children && c.children.length) out.push(...flatCats(c.children));
|
|
630
|
+
}
|
|
631
|
+
return out;
|
|
632
|
+
}
|
|
633
|
+
for (const c of flatCats(list)) {
|
|
634
|
+
const opt = document.createElement('option');
|
|
635
|
+
opt.value = String(c.cid);
|
|
636
|
+
opt.textContent = String(c.name || c.cid);
|
|
637
|
+
catSelect.appendChild(opt);
|
|
638
|
+
}
|
|
639
|
+
// Re-apply saved value (fillForm ran before options were added)
|
|
640
|
+
if (s && s.specialEventCategoryId) catSelect.value = String(s.specialEventCategoryId);
|
|
641
|
+
} catch (e) { /* non-blocking */ }
|
|
642
|
+
}
|
|
643
|
+
|
|
618
644
|
// Ensure default creator group prefix appears in the ACP field
|
|
619
645
|
const y = new Date().getFullYear();
|
|
620
646
|
const defaultGroup = `onekite-ffvl-${y}`;
|
package/public/client.js
CHANGED
|
@@ -239,8 +239,32 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
239
239
|
return opts.join('');
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
function buildInitialValuesFromEvent(details) {
|
|
243
|
+
function toLocal(ts) {
|
|
244
|
+
const d = new Date(parseInt(ts, 10));
|
|
245
|
+
return {
|
|
246
|
+
ymd: `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`,
|
|
247
|
+
time: `${pad2(d.getHours())}:${pad2(d.getMinutes())}`,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
const s = toLocal(details.start);
|
|
251
|
+
const e = toLocal(details.end);
|
|
252
|
+
return {
|
|
253
|
+
title: details.title || '',
|
|
254
|
+
startYmd: s.ymd,
|
|
255
|
+
startTime: s.time,
|
|
256
|
+
endYmd: e.ymd,
|
|
257
|
+
endTime: e.time,
|
|
258
|
+
address: details.address || details.pickupAddress || '',
|
|
259
|
+
lat: details.lat || details.pickupLat || '',
|
|
260
|
+
lon: details.lon || details.pickupLon || '',
|
|
261
|
+
notes: details.notes || '',
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
242
265
|
async function openSpecialEventDialog(selectionInfo, opts) {
|
|
243
266
|
const kind = (opts && opts.kind) ? String(opts.kind) : 'special';
|
|
267
|
+
const iv = opts && opts.initialValues;
|
|
244
268
|
const start = selectionInfo.start;
|
|
245
269
|
// FullCalendar can omit `end` for certain interactions. Also, for all-day
|
|
246
270
|
// selections, `end` is exclusive (next day at 00:00). We normalise below
|
|
@@ -289,12 +313,20 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
289
313
|
}
|
|
290
314
|
}
|
|
291
315
|
|
|
316
|
+
// Edit mode: override computed dates with existing event values.
|
|
317
|
+
if (iv) {
|
|
318
|
+
if (iv.startYmd && iv.startTime) seStart = new Date(`${iv.startYmd}T${iv.startTime}`);
|
|
319
|
+
if (iv.endYmd && iv.endTime) seEnd = new Date(`${iv.endYmd}T${iv.endTime}`);
|
|
320
|
+
}
|
|
321
|
+
|
|
292
322
|
const seStartTime = timeString(seStart);
|
|
293
323
|
const seEndTime = timeString(seEnd);
|
|
294
|
-
// Same UX for all types: an example placeholder, but never pre-fill the value.
|
|
295
|
-
// If left empty, the server can still apply its own default label.
|
|
296
324
|
const defaultTitlePlaceholder = 'Ex: ...';
|
|
297
|
-
const defaultTitleValue = '';
|
|
325
|
+
const defaultTitleValue = iv ? (iv.title || '') : '';
|
|
326
|
+
const ivAddress = iv ? (iv.address || '') : '';
|
|
327
|
+
const ivLat = iv ? (iv.lat || '') : '';
|
|
328
|
+
const ivLon = iv ? (iv.lon || '') : '';
|
|
329
|
+
const ivNotes = iv ? (iv.notes || '') : '';
|
|
298
330
|
|
|
299
331
|
const html = `
|
|
300
332
|
<div class="mb-3">
|
|
@@ -328,23 +360,32 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
328
360
|
<div class="mt-3">
|
|
329
361
|
<label class="form-label">Adresse</label>
|
|
330
362
|
<div class="input-group">
|
|
331
|
-
<input type="text" class="form-control" id="onekite-se-address" placeholder="Adresse complète" />
|
|
363
|
+
<input type="text" class="form-control" id="onekite-se-address" placeholder="Adresse complète" value="${escapeHtml(ivAddress)}" />
|
|
332
364
|
<button class="btn btn-outline-secondary" type="button" id="onekite-se-geocode">Rechercher</button>
|
|
333
365
|
</div>
|
|
334
366
|
<div id="onekite-se-map" style="height:220px; border:1px solid #ddd; border-radius:6px; margin-top:0.5rem;"></div>
|
|
335
|
-
<input type="hidden" id="onekite-se-lat" />
|
|
336
|
-
<input type="hidden" id="onekite-se-lon" />
|
|
367
|
+
<input type="hidden" id="onekite-se-lat" value="${escapeHtml(ivLat)}" />
|
|
368
|
+
<input type="hidden" id="onekite-se-lon" value="${escapeHtml(ivLon)}" />
|
|
337
369
|
</div>
|
|
338
370
|
<div class="mt-3">
|
|
339
371
|
<label class="form-label">Notes (facultatif)</label>
|
|
340
|
-
<textarea class="form-control" id="onekite-se-notes" rows="3" placeholder="..."
|
|
372
|
+
<textarea class="form-control" id="onekite-se-notes" rows="3" placeholder="...">${escapeHtml(ivNotes)}</textarea>
|
|
373
|
+
</div>
|
|
374
|
+
${(kind !== 'outing' && opts && opts.withContent) ? `
|
|
375
|
+
<div class="mt-3">
|
|
376
|
+
<label class="form-label">Message (facultatif)</label>
|
|
377
|
+
<textarea class="form-control" id="onekite-se-content" rows="4" placeholder="Corps du sujet qui sera publié dans le forum (brouillon)..."></textarea>
|
|
341
378
|
</div>
|
|
379
|
+
` : ''}
|
|
342
380
|
`;
|
|
343
381
|
|
|
344
382
|
return await new Promise((resolve) => {
|
|
345
383
|
let resolved = false;
|
|
384
|
+
const isEditMode = !!iv;
|
|
346
385
|
const dialog = bootbox.dialog({
|
|
347
|
-
title:
|
|
386
|
+
title: isEditMode
|
|
387
|
+
? (kind === 'outing' ? 'Modifier la sortie' : "Modifier l'évènement")
|
|
388
|
+
: (kind === 'outing' ? 'Créer une prévision de sortie' : 'Créer un évènement'),
|
|
348
389
|
message: html,
|
|
349
390
|
buttons: {
|
|
350
391
|
cancel: {
|
|
@@ -356,7 +397,7 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
356
397
|
},
|
|
357
398
|
},
|
|
358
399
|
ok: {
|
|
359
|
-
label: 'Créer',
|
|
400
|
+
label: isEditMode ? 'Enregistrer' : 'Créer',
|
|
360
401
|
className: 'btn-primary',
|
|
361
402
|
callback: () => {
|
|
362
403
|
const title = (document.getElementById('onekite-se-title')?.value || '').trim();
|
|
@@ -392,8 +433,11 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
392
433
|
const notes = (document.getElementById('onekite-se-notes')?.value || '').trim();
|
|
393
434
|
const lat = (document.getElementById('onekite-se-lat')?.value || '').trim();
|
|
394
435
|
const lon = (document.getElementById('onekite-se-lon')?.value || '').trim();
|
|
436
|
+
const content = (kind !== 'outing' && opts && opts.withContent)
|
|
437
|
+
? (document.getElementById('onekite-se-content')?.value || '').trim()
|
|
438
|
+
: '';
|
|
395
439
|
resolved = true;
|
|
396
|
-
resolve({ title, start: startVal, end: endVal, address, notes, lat, lon });
|
|
440
|
+
resolve({ title, start: startVal, end: endVal, address, notes, lat, lon, content });
|
|
397
441
|
return true;
|
|
398
442
|
},
|
|
399
443
|
},
|
|
@@ -437,6 +481,14 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
437
481
|
document.getElementById('onekite-se-lon').value = String(lon);
|
|
438
482
|
map.setView([lat, lon], 14);
|
|
439
483
|
}
|
|
484
|
+
// Pre-fill marker if editing an event with existing coordinates.
|
|
485
|
+
if (ivLat && ivLon) {
|
|
486
|
+
const initLat = Number(ivLat);
|
|
487
|
+
const initLon = Number(ivLon);
|
|
488
|
+
if (Number.isFinite(initLat) && Number.isFinite(initLon)) {
|
|
489
|
+
setMarker(initLat, initLon);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
440
492
|
const geocodeBtn = document.getElementById('onekite-se-geocode');
|
|
441
493
|
const addrInput = document.getElementById('onekite-se-address');
|
|
442
494
|
try {
|
|
@@ -1225,6 +1277,7 @@ function toDatetimeLocalValue(date) {
|
|
|
1225
1277
|
const canDeleteSpecial = !!caps.canDeleteSpecial;
|
|
1226
1278
|
const canCreateOuting = !!caps.canCreateOuting;
|
|
1227
1279
|
const canCreateReservation = !!caps.canCreateReservation;
|
|
1280
|
+
const specialEventCategoryCid = parseInt(caps.specialEventCategoryCid, 10) || 0;
|
|
1228
1281
|
|
|
1229
1282
|
// Creation chooser: Location / Prévision de sortie / Évènement (si autorisé).
|
|
1230
1283
|
|
|
@@ -1415,15 +1468,23 @@ function toDatetimeLocalValue(date) {
|
|
|
1415
1468
|
},
|
|
1416
1469
|
onCreateSpecial: async (sel) => {
|
|
1417
1470
|
try {
|
|
1418
|
-
const payload = await openSpecialEventDialog(sel);
|
|
1471
|
+
const payload = await openSpecialEventDialog(sel, { withContent: specialEventCategoryCid > 0 });
|
|
1419
1472
|
if (!payload) return;
|
|
1420
|
-
await fetchJson('/api/v3/plugins/calendar-onekite/special-events', {
|
|
1473
|
+
const resp = await fetchJson('/api/v3/plugins/calendar-onekite/special-events', {
|
|
1421
1474
|
method: 'POST',
|
|
1422
1475
|
body: JSON.stringify(payload),
|
|
1423
1476
|
});
|
|
1424
1477
|
showAlert('success', 'Évènement créé.');
|
|
1425
1478
|
invalidateEventsCache();
|
|
1426
1479
|
scheduleRefetch(calendar);
|
|
1480
|
+
const cid = (resp && resp.categoryCid) ? parseInt(resp.categoryCid, 10) : specialEventCategoryCid;
|
|
1481
|
+
if (payload.content && cid > 0) {
|
|
1482
|
+
try {
|
|
1483
|
+
require(['composer'], (composer) => {
|
|
1484
|
+
composer.newTopic({ cid, title: payload.title || 'Évènement', body: payload.content });
|
|
1485
|
+
});
|
|
1486
|
+
} catch (e) {}
|
|
1487
|
+
}
|
|
1427
1488
|
} catch (e) {
|
|
1428
1489
|
handleCreateError(e);
|
|
1429
1490
|
} finally {
|
|
@@ -1495,15 +1556,23 @@ function toDatetimeLocalValue(date) {
|
|
|
1495
1556
|
},
|
|
1496
1557
|
onCreateSpecial: async (sel) => {
|
|
1497
1558
|
try {
|
|
1498
|
-
const payload = await openSpecialEventDialog(sel);
|
|
1559
|
+
const payload = await openSpecialEventDialog(sel, { withContent: specialEventCategoryCid > 0 });
|
|
1499
1560
|
if (!payload) return;
|
|
1500
|
-
await fetchJson('/api/v3/plugins/calendar-onekite/special-events', {
|
|
1561
|
+
const resp = await fetchJson('/api/v3/plugins/calendar-onekite/special-events', {
|
|
1501
1562
|
method: 'POST',
|
|
1502
1563
|
body: JSON.stringify(payload),
|
|
1503
1564
|
});
|
|
1504
1565
|
showAlert('success', 'Évènement créé.');
|
|
1505
1566
|
invalidateEventsCache();
|
|
1506
1567
|
scheduleRefetch(calendar);
|
|
1568
|
+
const cid = (resp && resp.categoryCid) ? parseInt(resp.categoryCid, 10) : specialEventCategoryCid;
|
|
1569
|
+
if (payload.content && cid > 0) {
|
|
1570
|
+
try {
|
|
1571
|
+
require(['composer'], (composer) => {
|
|
1572
|
+
composer.newTopic({ cid, title: payload.title || 'Évènement', body: payload.content });
|
|
1573
|
+
});
|
|
1574
|
+
} catch (e) {}
|
|
1575
|
+
}
|
|
1507
1576
|
} catch (e) {
|
|
1508
1577
|
const msg = (e && e.payload && (e.payload.message || e.payload.error || e.payload.msg)) ? String(e.payload.message || e.payload.error || e.payload.msg) : '';
|
|
1509
1578
|
showAlert('error', msg || 'Erreur lors de la création.');
|
|
@@ -1737,11 +1806,39 @@ function toDatetimeLocalValue(date) {
|
|
|
1737
1806
|
${notes ? `<div class="mb-2"><strong>Notes</strong><br>${escapeHtml(notes).replace(/\n/g,'<br>')}</div>` : ''}
|
|
1738
1807
|
`;
|
|
1739
1808
|
const canDel = !!(p.canDeleteSpecial || canDeleteSpecial);
|
|
1809
|
+
const canEdit = !!p.canEditSpecial;
|
|
1740
1810
|
const dlg = bootbox.dialog({
|
|
1741
1811
|
title: 'Évènement',
|
|
1742
1812
|
message: html,
|
|
1743
1813
|
buttons: {
|
|
1744
1814
|
close: { label: 'Fermer', className: 'btn-secondary' },
|
|
1815
|
+
...(canEdit ? {
|
|
1816
|
+
edit: {
|
|
1817
|
+
label: 'Éditer',
|
|
1818
|
+
className: 'btn-default',
|
|
1819
|
+
callback: () => {
|
|
1820
|
+
(async () => {
|
|
1821
|
+
try { dlg.modal('hide'); } catch (e) { try { bootbox.hideAll(); } catch (e2) {} }
|
|
1822
|
+
const eid = String(p.eid || ev.id).replace(/^special:/, '');
|
|
1823
|
+
const initialValues = buildInitialValuesFromEvent(p);
|
|
1824
|
+
const payload = await openSpecialEventDialog({}, { kind: 'special', initialValues });
|
|
1825
|
+
if (!payload) return;
|
|
1826
|
+
try {
|
|
1827
|
+
await fetchJson(`/api/v3/plugins/calendar-onekite/special-events/${encodeURIComponent(eid)}`, {
|
|
1828
|
+
method: 'PUT',
|
|
1829
|
+
body: JSON.stringify(payload),
|
|
1830
|
+
});
|
|
1831
|
+
showAlert('success', 'Évènement mis à jour.');
|
|
1832
|
+
invalidateEventsCache();
|
|
1833
|
+
scheduleRefetch(calendar);
|
|
1834
|
+
} catch (e) {
|
|
1835
|
+
showAlert('error', 'Mise à jour impossible.');
|
|
1836
|
+
}
|
|
1837
|
+
})();
|
|
1838
|
+
return false;
|
|
1839
|
+
},
|
|
1840
|
+
},
|
|
1841
|
+
} : {}),
|
|
1745
1842
|
...(canDel ? {
|
|
1746
1843
|
del: {
|
|
1747
1844
|
label: 'Supprimer',
|
|
@@ -1887,11 +1984,39 @@ function toDatetimeLocalValue(date) {
|
|
|
1887
1984
|
${notes ? `<div class="mb-2"><strong>Notes</strong><br>${escapeHtml(notes).replace(/\n/g,'<br>')}</div>` : ''}
|
|
1888
1985
|
`;
|
|
1889
1986
|
const canDel = !!(p.canDeleteOuting);
|
|
1987
|
+
const canEditOuting = !!p.canEditOuting;
|
|
1890
1988
|
const dlg = bootbox.dialog({
|
|
1891
1989
|
title: 'Sortie',
|
|
1892
1990
|
message: html,
|
|
1893
1991
|
buttons: {
|
|
1894
1992
|
close: { label: 'Fermer', className: 'btn-secondary' },
|
|
1993
|
+
...(canEditOuting ? {
|
|
1994
|
+
edit: {
|
|
1995
|
+
label: 'Éditer',
|
|
1996
|
+
className: 'btn-default',
|
|
1997
|
+
callback: () => {
|
|
1998
|
+
(async () => {
|
|
1999
|
+
try { dlg.modal('hide'); } catch (e) { try { bootbox.hideAll(); } catch (e2) {} }
|
|
2000
|
+
const oid = String(p.oid || ev.id).replace(/^outing:/, '');
|
|
2001
|
+
const initialValues = buildInitialValuesFromEvent(p);
|
|
2002
|
+
const payload = await openSpecialEventDialog({}, { kind: 'outing', initialValues });
|
|
2003
|
+
if (!payload) return;
|
|
2004
|
+
try {
|
|
2005
|
+
await fetchJson(`/api/v3/plugins/calendar-onekite/outings/${encodeURIComponent(oid)}`, {
|
|
2006
|
+
method: 'PUT',
|
|
2007
|
+
body: JSON.stringify(payload),
|
|
2008
|
+
});
|
|
2009
|
+
showAlert('success', 'Sortie mise à jour.');
|
|
2010
|
+
invalidateEventsCache();
|
|
2011
|
+
scheduleRefetch(calendar);
|
|
2012
|
+
} catch (e) {
|
|
2013
|
+
showAlert('error', 'Mise à jour impossible.');
|
|
2014
|
+
}
|
|
2015
|
+
})();
|
|
2016
|
+
return false;
|
|
2017
|
+
},
|
|
2018
|
+
},
|
|
2019
|
+
} : {}),
|
|
1895
2020
|
...(canDel ? {
|
|
1896
2021
|
del: {
|
|
1897
2022
|
label: 'Supprimer',
|
|
@@ -161,11 +161,13 @@
|
|
|
161
161
|
<h4>Évènements (autre couleur)</h4>
|
|
162
162
|
<div class="form-text mb-3">Permet de créer des évènements horaires (début/fin) avec adresse (Leaflet) et notes.</div>
|
|
163
163
|
|
|
164
|
-
<h4 class="mt-3">
|
|
164
|
+
<h4 class="mt-3">Publication forum</h4>
|
|
165
165
|
<div class="mb-3">
|
|
166
|
-
<label class="form-label">
|
|
167
|
-
<
|
|
168
|
-
|
|
166
|
+
<label class="form-label">Catégorie de publication des évènements</label>
|
|
167
|
+
<select class="form-select" name="specialEventCategoryId" id="onekite-special-category-select">
|
|
168
|
+
<option value="">— Désactivé —</option>
|
|
169
|
+
</select>
|
|
170
|
+
<div class="form-text">Si une catégorie est sélectionnée, un champ "Message" apparaît dans la modale de création. À la validation, le compositeur NodeBB s'ouvre pré-rempli pour que vous puissiez relire et publier.</div>
|
|
169
171
|
</div>
|
|
170
172
|
|
|
171
173
|
<div class="mb-3">
|