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 +31 -0
- package/lib/widgets.js +38 -5
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/public/client.js +1 -1
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
|
|
131
|
-
|
|
132
|
-
|
|
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
package/plugin.json
CHANGED
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',
|