nodebb-plugin-onekite-calendar 2.0.73 → 2.0.74

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
@@ -1465,6 +1465,22 @@ api.createReservation = async function (req, res) {
1465
1465
  res.json({ ok: true, rid, status: resv.status, autoPaid: !!(isValidatorFree || isRegularization) });
1466
1466
  };
1467
1467
 
1468
+ api.searchUsers = async function (req, res) {
1469
+ const uid = req.uid;
1470
+ if (!uid) return res.status(401).json({ error: 'not-logged-in' });
1471
+ const settings = await getSettings();
1472
+ if (!(await canValidate(uid, settings))) return res.status(403).json({ error: 'not-allowed' });
1473
+ const q = String(req.query.q || '').trim();
1474
+ if (q.length < 2) return res.json([]);
1475
+ try {
1476
+ const result = await user.search({ query: q, searchBy: 'username', resultsPerPage: 8, uid });
1477
+ const list = (result && result.users) ? result.users : [];
1478
+ return res.json(list.map(u => ({ uid: u.uid, username: u.username })));
1479
+ } catch (e) {
1480
+ return res.json([]);
1481
+ }
1482
+ };
1483
+
1468
1484
  // Validator actions (from calendar popup)
1469
1485
  api.approveReservation = async function (req, res) {
1470
1486
  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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-onekite-calendar",
3
- "version": "2.0.73",
3
+ "version": "2.0.74",
4
4
  "description": "FullCalendar-based equipment reservation workflow with admin approval & HelloAsso payment for NodeBB",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
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');