nodebb-plugin-onekite-calendar 1.0.27 → 1.0.29

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
@@ -404,6 +404,7 @@ api.getEvents = async function (req, res) {
404
404
 
405
405
  const settings = await meta.settings.get('calendar-onekite');
406
406
  const canMod = req.uid ? await canValidate(req.uid, settings) : false;
407
+ const widgetMode = String((req.query && req.query.widget) || '') === '1';
407
408
  const canSpecialCreate = req.uid ? await canCreateSpecial(req.uid, settings) : false;
408
409
  const canSpecialDelete = req.uid ? await canDeleteSpecial(req.uid, settings) : false;
409
410
 
@@ -414,8 +415,37 @@ api.getEvents = async function (req, res) {
414
415
  const out = [];
415
416
  // Batch fetch = major perf win when there are many reservations.
416
417
  const reservations = await dbLayer.getReservations(ids);
418
+
419
+ // Username map (needed for widget tooltip: "Réservée par ...")
420
+ let uidToUsername = {};
421
+ try {
422
+ const uids = Array.from(new Set((reservations || [])
423
+ .map((r) => String((r && r.uid) || ''))
424
+ .filter(Boolean)));
425
+ if (uids.length) {
426
+ let rows = [];
427
+ if (typeof user.getUsersFields === 'function') {
428
+ rows = await user.getUsersFields(uids, ['username']);
429
+ // Some NodeBB versions omit uid in returned rows; re-attach from input order.
430
+ if (Array.isArray(rows)) {
431
+ rows = rows.map((row, idx) => (row ? Object.assign({ uid: uids[idx] }, row) : null));
432
+ }
433
+ } else {
434
+ rows = await Promise.all(uids.map(async (uid) => {
435
+ try { return Object.assign({ uid }, await user.getUserFields(uid, ['username'])); } catch (e) { return { uid, username: '' }; }
436
+ }));
437
+ }
438
+ uidToUsername = (rows || []).reduce((acc, row) => {
439
+ if (row && row.uid) acc[String(row.uid)] = String(row.username || '');
440
+ return acc;
441
+ }, {});
442
+ }
443
+ } catch (e) {}
417
444
  for (const r of (reservations || [])) {
418
445
  if (!r) continue;
446
+ if (!r.username && r.uid && uidToUsername[String(r.uid)]) {
447
+ r.username = uidToUsername[String(r.uid)];
448
+ }
419
449
  // Only show active statuses
420
450
  if (!['pending', 'awaiting_payment', 'paid'].includes(r.status)) continue;
421
451
  const rStart = parseInt(r.start, 10);
@@ -439,6 +469,7 @@ api.getEvents = async function (req, res) {
439
469
  status: p.status,
440
470
  uid: p.uid,
441
471
  canModerate: canMod,
472
+ ...(widgetMode ? { reservedByUsername: String(r.username || '') } : {}),
442
473
  },
443
474
  };
444
475
  // Only expose username on the event list to owner/moderators.
package/lib/widgets.js CHANGED
@@ -127,10 +127,41 @@ widgets.renderTwoWeeksWidget = async function (data) {
127
127
  tip.innerHTML = html;
128
128
  }
129
129
 
130
- function showTipAt(x, y) {
131
- tip.style.left = Math.max(8, x + 12) + 'px';
132
- tip.style.top = Math.max(8, y + 12) + 'px';
130
+ function clamp(n, min, max) {
131
+ return Math.max(min, Math.min(n, max));
132
+ }
133
+
134
+ function showTipAt(pageX, pageY) {
135
+ // Robust mobile positioning: keep the tooltip inside the viewport.
136
+ const pad = 8;
137
+
138
+ // Ensure the tooltip is measurable.
139
+ tip.style.maxWidth = 'calc(100vw - 16px)';
133
140
  tip.style.display = 'block';
141
+ tip.style.visibility = 'hidden';
142
+
143
+ const rect = tip.getBoundingClientRect();
144
+ const vw = window.innerWidth || document.documentElement.clientWidth || 0;
145
+ const vh = window.innerHeight || document.documentElement.clientHeight || 0;
146
+ const sx = window.scrollX || window.pageXOffset || 0;
147
+ const sy = window.scrollY || window.pageYOffset || 0;
148
+
149
+ // Default: below the pointer/anchor, centered.
150
+ let left = pageX - (rect.width / 2);
151
+ let top = pageY + 12;
152
+
153
+ // If it would go below the viewport, try above.
154
+ if ((top - sy + rect.height) > (vh - pad)) {
155
+ top = pageY - rect.height - 12;
156
+ }
157
+
158
+ // Clamp inside viewport (document coordinates).
159
+ left = clamp(left, sx + pad, sx + vw - rect.width - pad);
160
+ top = clamp(top, sy + pad, sy + vh - rect.height - pad);
161
+
162
+ tip.style.left = left + 'px';
163
+ tip.style.top = top + 'px';
164
+ tip.style.visibility = 'visible';
134
165
  }
135
166
 
136
167
  function hideTip() {
@@ -241,7 +272,7 @@ widgets.renderTwoWeeksWidget = async function (data) {
241
272
  return { domNodes: [wrap] };
242
273
  },
243
274
  events: function(info, successCallback, failureCallback) {
244
- const qs = new URLSearchParams({ start: info.startStr, end: info.endStr });
275
+ const qs = new URLSearchParams({ start: info.startStr, end: info.endStr, widget: '1' });
245
276
  fetch(eventsEndpoint + '?' + qs.toString(), { credentials: 'same-origin' })
246
277
  .then((r) => r.json())
247
278
  .then((json) => successCallback(json || []))
@@ -279,7 +310,7 @@ dateClick: function() { window.location.href = calUrl; },
279
310
  const title = (ep.itemNameLine || ep.title || ev.title || '').toString();
280
311
  const statusLabel = (function(s){
281
312
  const map = {
282
- pending: 'En attente',
313
+ pending: 'En attente de validation',
283
314
  awaiting_payment: 'Validée – paiement en attente',
284
315
  paid: 'Payée',
285
316
  rejected: 'Rejetée',
@@ -289,8 +320,10 @@ dateClick: function() { window.location.href = calUrl; },
289
320
  return map[k] || '';
290
321
  });
291
322
  const status = (String(ep.type || '') === 'reservation') ? statusLabel(ep.status) : '';
323
+ const reservedBy = (String(ep.type || '') === 'reservation') ? String(ep.reservedByUsername || '') : '';
292
324
  const html = '' +
293
325
  '<div style="font-weight:600; margin-bottom:2px;">' + escapeHtml(title) + '</div>' +
326
+ (reservedBy ? ('<div style="opacity:.85; margin-top:2px; font-size:.85em;">Réservée par ' + escapeHtml(reservedBy) + '.</div>') : '') +
294
327
  (status ? ('<div style="opacity:.75; margin-top:2px; font-size:.85em;">' + escapeHtml(status) + '</div>') : '');
295
328
 
296
329
  // Hover (desktop)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-onekite-calendar",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
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/plugin.json CHANGED
@@ -39,5 +39,5 @@
39
39
  "acpScripts": [
40
40
  "public/admin.js"
41
41
  ],
42
- "version": "1.0.26"
42
+ "version": "1.0.29"
43
43
  }
package/public/client.js CHANGED
@@ -387,7 +387,7 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
387
387
 
388
388
  function statusLabel(s) {
389
389
  const map = {
390
- pending: 'En attente',
390
+ pending: 'En attente de validation',
391
391
  awaiting_payment: 'Validée – paiement en attente',
392
392
  paid: 'Payée',
393
393
  rejected: 'Rejetée',