nodebb-plugin-onekite-calendar 2.0.73 → 2.0.75
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 +38 -25
- package/library.js +1 -0
- package/package.json +1 -1
- package/public/client.js +105 -0
package/lib/api.js
CHANGED
|
@@ -778,7 +778,7 @@ api.getCapabilities = async function (req, res) {
|
|
|
778
778
|
isValidator: canMod,
|
|
779
779
|
canCreateSpecial: canSpecialC,
|
|
780
780
|
canDeleteSpecial: canSpecialD,
|
|
781
|
-
canCreateOuting: canReq,
|
|
781
|
+
canCreateOuting: canMod || canReq,
|
|
782
782
|
canCreateReservation: canReq,
|
|
783
783
|
specialEventCategoryCid: parseInt(settings && settings.specialEventCategoryId, 10) || 0,
|
|
784
784
|
});
|
|
@@ -938,7 +938,7 @@ api.getOutingDetails = async function (req, res) {
|
|
|
938
938
|
});
|
|
939
939
|
|
|
940
940
|
const participants = normalizeUidList(o.participants);
|
|
941
|
-
const canEditOuting = uid ? await canRequest(uid, settings, Date.now()) : false;
|
|
941
|
+
const canEditOuting = uid ? (canMod || await canRequest(uid, settings, Date.now())) : false;
|
|
942
942
|
const out = {
|
|
943
943
|
oid: o.oid,
|
|
944
944
|
title: o.title || '',
|
|
@@ -967,8 +967,7 @@ api.joinOuting = async function (req, res) {
|
|
|
967
967
|
const settings = await getSettings();
|
|
968
968
|
const uid = req.uid;
|
|
969
969
|
if (!uid) return res.status(401).json({ error: 'not-logged-in' });
|
|
970
|
-
|
|
971
|
-
const ok = await canRequest(uid, settings, Date.now());
|
|
970
|
+
const ok = (await canValidate(uid, settings)) || (await canRequest(uid, settings, Date.now()));
|
|
972
971
|
if (!ok) return res.status(403).json({ error: 'not-allowed' });
|
|
973
972
|
|
|
974
973
|
const oid = String(req.params.oid || '').replace(/^outing:/, '').trim();
|
|
@@ -999,8 +998,7 @@ api.leaveOuting = async function (req, res) {
|
|
|
999
998
|
const settings = await getSettings();
|
|
1000
999
|
const uid = req.uid;
|
|
1001
1000
|
if (!uid) return res.status(401).json({ error: 'not-logged-in' });
|
|
1002
|
-
|
|
1003
|
-
const ok = await canRequest(uid, settings, Date.now());
|
|
1001
|
+
const ok = (await canValidate(uid, settings)) || (await canRequest(uid, settings, Date.now()));
|
|
1004
1002
|
if (!ok) return res.status(403).json({ error: 'not-allowed' });
|
|
1005
1003
|
|
|
1006
1004
|
const oid = String(req.params.oid || '').replace(/^outing:/, '').trim();
|
|
@@ -1032,12 +1030,9 @@ api.createOuting = async function (req, res) {
|
|
|
1032
1030
|
if (!req.uid) return res.status(401).json({ error: 'not-logged-in' });
|
|
1033
1031
|
|
|
1034
1032
|
const startTs = toTs(req.body && req.body.start);
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
// requiring next-year group membership.
|
|
1039
|
-
const ok = await canRequest(req.uid, settings, Date.now());
|
|
1040
|
-
if (!ok) {
|
|
1033
|
+
const isValidatorForOuting = await canValidate(req.uid, settings);
|
|
1034
|
+
const canMakeOuting = isValidatorForOuting || (await canRequest(req.uid, settings, Date.now()));
|
|
1035
|
+
if (!canMakeOuting) {
|
|
1041
1036
|
return res.status(403).json({
|
|
1042
1037
|
error: 'not-allowed',
|
|
1043
1038
|
code: 'NOT_MEMBER',
|
|
@@ -1051,18 +1046,20 @@ api.createOuting = async function (req, res) {
|
|
|
1051
1046
|
return res.status(400).json({ error: 'bad-dates' });
|
|
1052
1047
|
}
|
|
1053
1048
|
|
|
1054
|
-
//
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1049
|
+
// Validators can create outings in the past (regularization); others cannot.
|
|
1050
|
+
if (!isValidatorForOuting) {
|
|
1051
|
+
try {
|
|
1052
|
+
const today0 = new Date();
|
|
1053
|
+
today0.setHours(0, 0, 0, 0);
|
|
1054
|
+
const today0ts = today0.getTime();
|
|
1055
|
+
if (startTs < today0ts) {
|
|
1056
|
+
return res.status(400).json({
|
|
1057
|
+
error: 'date-too-soon',
|
|
1058
|
+
message: "Impossible de créer pour une date passée.",
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
} catch (e) {}
|
|
1062
|
+
}
|
|
1066
1063
|
|
|
1067
1064
|
const address = String((req.body && req.body.address) || '').trim();
|
|
1068
1065
|
const notes = String((req.body && req.body.notes) || '').trim();
|
|
@@ -1122,7 +1119,7 @@ api.deleteOuting = async function (req, res) {
|
|
|
1122
1119
|
api.updateOuting = async function (req, res) {
|
|
1123
1120
|
const settings = await getSettings();
|
|
1124
1121
|
if (!req.uid) return res.status(401).json({ error: 'not-logged-in' });
|
|
1125
|
-
const ok = await canRequest(req.uid, settings, Date.now());
|
|
1122
|
+
const ok = (await canValidate(req.uid, settings)) || (await canRequest(req.uid, settings, Date.now()));
|
|
1126
1123
|
if (!ok) return res.status(403).json({ error: 'not-allowed' });
|
|
1127
1124
|
|
|
1128
1125
|
const oid = String(req.params.oid || '').replace(/^outing:/, '').trim();
|
|
@@ -1465,6 +1462,22 @@ api.createReservation = async function (req, res) {
|
|
|
1465
1462
|
res.json({ ok: true, rid, status: resv.status, autoPaid: !!(isValidatorFree || isRegularization) });
|
|
1466
1463
|
};
|
|
1467
1464
|
|
|
1465
|
+
api.searchUsers = async function (req, res) {
|
|
1466
|
+
const uid = req.uid;
|
|
1467
|
+
if (!uid) return res.status(401).json({ error: 'not-logged-in' });
|
|
1468
|
+
const settings = await getSettings();
|
|
1469
|
+
if (!(await canValidate(uid, settings))) return res.status(403).json({ error: 'not-allowed' });
|
|
1470
|
+
const q = String(req.query.q || '').trim();
|
|
1471
|
+
if (q.length < 2) return res.json([]);
|
|
1472
|
+
try {
|
|
1473
|
+
const result = await user.search({ query: q, searchBy: 'username', resultsPerPage: 8, uid });
|
|
1474
|
+
const list = (result && result.users) ? result.users : [];
|
|
1475
|
+
return res.json(list.map(u => ({ uid: u.uid, username: u.username })));
|
|
1476
|
+
} catch (e) {
|
|
1477
|
+
return res.json([]);
|
|
1478
|
+
}
|
|
1479
|
+
};
|
|
1480
|
+
|
|
1468
1481
|
// Validator actions (from calendar popup)
|
|
1469
1482
|
api.approveReservation = async function (req, res) {
|
|
1470
1483
|
const uid = req.uid;
|
package/library.js
CHANGED
|
@@ -65,6 +65,7 @@ Plugin.init = async function (params) {
|
|
|
65
65
|
router.get('/api/v3/plugins/calendar-onekite/events', ...publicExpose, api.getEvents);
|
|
66
66
|
router.get('/api/v3/plugins/calendar-onekite/items', ...publicExpose, api.getItems);
|
|
67
67
|
router.get('/api/v3/plugins/calendar-onekite/capabilities', ...publicExpose, api.getCapabilities);
|
|
68
|
+
router.get('/api/v3/plugins/calendar-onekite/users/search', ...publicExpose, api.searchUsers);
|
|
68
69
|
|
|
69
70
|
// Maintenance / audit (restricted to validator groups)
|
|
70
71
|
router.get('/api/v3/plugins/calendar-onekite/maintenance', ...publicExpose, api.getMaintenance);
|
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -821,6 +821,107 @@ async function searchAddresses(query, limit) {
|
|
|
821
821
|
}).filter(h => h && h.displayName);
|
|
822
822
|
}
|
|
823
823
|
|
|
824
|
+
async function searchUsers(q, limit) {
|
|
825
|
+
try {
|
|
826
|
+
const qs = new URLSearchParams({ q: String(q || ''), limit: String(limit || 8) });
|
|
827
|
+
const arr = await fetchJson(`/api/v3/plugins/calendar-onekite/users/search?${qs}`);
|
|
828
|
+
if (!Array.isArray(arr)) return [];
|
|
829
|
+
return arr;
|
|
830
|
+
} catch (e) {
|
|
831
|
+
return [];
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function attachUserAutocomplete(inputEl) {
|
|
836
|
+
if (!inputEl) return;
|
|
837
|
+
if (inputEl.getAttribute('data-onekite-user-ac') === '1') return;
|
|
838
|
+
inputEl.setAttribute('data-onekite-user-ac', '1');
|
|
839
|
+
|
|
840
|
+
const host = inputEl.parentNode || document.body;
|
|
841
|
+
try {
|
|
842
|
+
const cs = window.getComputedStyle(host);
|
|
843
|
+
if (!cs || cs.position === 'static') host.style.position = 'relative';
|
|
844
|
+
} catch (e) {
|
|
845
|
+
host.style.position = 'relative';
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
const menu = document.createElement('div');
|
|
849
|
+
menu.className = 'onekite-autocomplete-menu';
|
|
850
|
+
menu.style.position = 'absolute';
|
|
851
|
+
menu.style.left = '0';
|
|
852
|
+
menu.style.right = '0';
|
|
853
|
+
menu.style.top = '100%';
|
|
854
|
+
menu.style.zIndex = '2000';
|
|
855
|
+
menu.style.background = '#fff';
|
|
856
|
+
menu.style.border = '1px solid rgba(0,0,0,.15)';
|
|
857
|
+
menu.style.borderTop = '0';
|
|
858
|
+
menu.style.maxHeight = '220px';
|
|
859
|
+
menu.style.overflowY = 'auto';
|
|
860
|
+
menu.style.display = 'none';
|
|
861
|
+
menu.style.borderRadius = '0 0 .375rem .375rem';
|
|
862
|
+
host.appendChild(menu);
|
|
863
|
+
|
|
864
|
+
let timer = null;
|
|
865
|
+
let busy = false;
|
|
866
|
+
|
|
867
|
+
function hide() { menu.style.display = 'none'; menu.innerHTML = ''; }
|
|
868
|
+
|
|
869
|
+
function show(hits) {
|
|
870
|
+
if (!hits || !hits.length) { hide(); return; }
|
|
871
|
+
menu.innerHTML = '';
|
|
872
|
+
hits.forEach((h) => {
|
|
873
|
+
const btn = document.createElement('button');
|
|
874
|
+
btn.type = 'button';
|
|
875
|
+
btn.className = 'onekite-autocomplete-item';
|
|
876
|
+
btn.textContent = h.username;
|
|
877
|
+
btn.style.display = 'block';
|
|
878
|
+
btn.style.width = '100%';
|
|
879
|
+
btn.style.textAlign = 'left';
|
|
880
|
+
btn.style.padding = '.35rem .5rem';
|
|
881
|
+
btn.style.border = '0';
|
|
882
|
+
btn.style.background = 'transparent';
|
|
883
|
+
btn.style.cursor = 'pointer';
|
|
884
|
+
btn.addEventListener('click', () => { inputEl.value = h.username; hide(); });
|
|
885
|
+
btn.addEventListener('mouseenter', () => { btn.style.background = 'rgba(0,0,0,.05)'; });
|
|
886
|
+
btn.addEventListener('mouseleave', () => { btn.style.background = 'transparent'; });
|
|
887
|
+
menu.appendChild(btn);
|
|
888
|
+
});
|
|
889
|
+
menu.style.display = 'block';
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
async function run(q) {
|
|
893
|
+
if (busy) return;
|
|
894
|
+
busy = true;
|
|
895
|
+
try {
|
|
896
|
+
const hits = await searchUsers(q, 8);
|
|
897
|
+
if (String(inputEl.value || '').trim() !== q) return;
|
|
898
|
+
show(hits);
|
|
899
|
+
} catch (e) {
|
|
900
|
+
hide();
|
|
901
|
+
} finally {
|
|
902
|
+
busy = false;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
inputEl.addEventListener('input', () => {
|
|
907
|
+
const q = String(inputEl.value || '').trim();
|
|
908
|
+
if (timer) clearTimeout(timer);
|
|
909
|
+
if (q.length < 2) { hide(); return; }
|
|
910
|
+
timer = setTimeout(() => run(q), 250);
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
inputEl.addEventListener('focus', () => {
|
|
914
|
+
const q = String(inputEl.value || '').trim();
|
|
915
|
+
if (q.length >= 2) { if (timer) clearTimeout(timer); timer = setTimeout(() => run(q), 150); }
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
document.addEventListener('click', (e) => {
|
|
919
|
+
try { if (!host.contains(e.target)) hide(); } catch (err) {}
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
inputEl.addEventListener('keydown', (e) => { if (e.key === 'Escape') hide(); });
|
|
923
|
+
}
|
|
924
|
+
|
|
824
925
|
function attachAddressAutocomplete(inputEl, onPick) {
|
|
825
926
|
if (!inputEl) return;
|
|
826
927
|
// Avoid double attach
|
|
@@ -1187,6 +1288,10 @@ function toDatetimeLocalValue(date) {
|
|
|
1187
1288
|
|
|
1188
1289
|
// live total update
|
|
1189
1290
|
setTimeout(() => {
|
|
1291
|
+
if (isValidatorMode) {
|
|
1292
|
+
attachUserAutocomplete(document.getElementById('onekite-target-username'));
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1190
1295
|
const totalEl = document.getElementById('onekite-total');
|
|
1191
1296
|
const periodEl = document.getElementById('onekite-period');
|
|
1192
1297
|
const daysEl = document.getElementById('onekite-days');
|