nodebb-plugin-onekite-calendar 2.0.3 → 2.0.5
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/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/public/admin.js +169 -0
- package/public/client.js +16 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog – calendar-onekite
|
|
2
2
|
|
|
3
|
+
## 1.2.12
|
|
4
|
+
- Modales (calendrier + ACP) : autocomplete adresse rendu identique et compatible Bootstrap input-group (plus de wrapper qui casse l’affichage)
|
|
5
|
+
|
|
6
|
+
## 1.2.11
|
|
7
|
+
- ACP : ajout de la recherche automatique d’adresse (autocomplete Nominatim) dans la modale de validation (unitaire + batch), comme sur le calendrier
|
|
8
|
+
|
|
3
9
|
## 1.2.10
|
|
4
10
|
- HelloAsso : calcul des jours 100% fiable (différence en jours calendaires Y/M/J, sans dépendance aux heures/au fuseau/DST)
|
|
5
11
|
- FullCalendar : endDate traitée comme exclusive partout (UI + checkout)
|
package/package.json
CHANGED
package/plugin.json
CHANGED
package/public/admin.js
CHANGED
|
@@ -187,6 +187,154 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
187
187
|
return { lat, lon, displayName: hit.display_name || q };
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
// Lightweight address autocomplete (OpenStreetMap Nominatim)
|
|
191
|
+
// Designed to work inside Bootstrap "input-group" without moving DOM nodes.
|
|
192
|
+
async function searchAddresses(query, limit) {
|
|
193
|
+
const q = String(query || '').trim();
|
|
194
|
+
const lim = Math.min(10, Math.max(1, Number(limit) || 6));
|
|
195
|
+
if (q.length < 3) return [];
|
|
196
|
+
const url = `https://nominatim.openstreetmap.org/search?format=jsonv2&addressdetails=1&limit=${lim}&q=${encodeURIComponent(q)}`;
|
|
197
|
+
const res = await fetch(url, {
|
|
198
|
+
method: 'GET',
|
|
199
|
+
headers: {
|
|
200
|
+
'Accept': 'application/json',
|
|
201
|
+
'Accept-Language': 'fr',
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
if (!res.ok) return [];
|
|
205
|
+
const arr = await res.json();
|
|
206
|
+
if (!Array.isArray(arr)) return [];
|
|
207
|
+
return arr.map((hit) => {
|
|
208
|
+
const lat = Number(hit.lat);
|
|
209
|
+
const lon = Number(hit.lon);
|
|
210
|
+
return {
|
|
211
|
+
displayName: hit.display_name || q,
|
|
212
|
+
lat: Number.isFinite(lat) ? lat : null,
|
|
213
|
+
lon: Number.isFinite(lon) ? lon : null,
|
|
214
|
+
};
|
|
215
|
+
}).filter(h => h && h.displayName);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function attachAddressAutocomplete(inputEl, onPick) {
|
|
219
|
+
if (!inputEl) return;
|
|
220
|
+
if (inputEl.getAttribute('data-onekite-autocomplete') === '1') return;
|
|
221
|
+
inputEl.setAttribute('data-onekite-autocomplete', '1');
|
|
222
|
+
|
|
223
|
+
// In Bootstrap input-groups (especially in ACP), wrapping the input breaks layout.
|
|
224
|
+
// So we anchor the menu to the closest input-group (or parent) without moving the input.
|
|
225
|
+
const anchor = inputEl.closest && inputEl.closest('.input-group')
|
|
226
|
+
? inputEl.closest('.input-group')
|
|
227
|
+
: (inputEl.parentNode || document.body);
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const cs = window.getComputedStyle(anchor);
|
|
231
|
+
if (!cs || cs.position === 'static') {
|
|
232
|
+
anchor.style.position = 'relative';
|
|
233
|
+
}
|
|
234
|
+
} catch (e) {
|
|
235
|
+
anchor.style.position = 'relative';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const menu = document.createElement('div');
|
|
239
|
+
menu.className = 'onekite-autocomplete-menu';
|
|
240
|
+
menu.style.position = 'absolute';
|
|
241
|
+
menu.style.left = '0';
|
|
242
|
+
menu.style.right = '0';
|
|
243
|
+
menu.style.top = '100%';
|
|
244
|
+
menu.style.zIndex = '2000';
|
|
245
|
+
menu.style.background = '#fff';
|
|
246
|
+
menu.style.border = '1px solid rgba(0,0,0,.15)';
|
|
247
|
+
menu.style.borderTop = '0';
|
|
248
|
+
menu.style.maxHeight = '220px';
|
|
249
|
+
menu.style.overflowY = 'auto';
|
|
250
|
+
menu.style.display = 'none';
|
|
251
|
+
menu.style.borderRadius = '0 0 .375rem .375rem';
|
|
252
|
+
anchor.appendChild(menu);
|
|
253
|
+
|
|
254
|
+
let timer = null;
|
|
255
|
+
let lastQuery = '';
|
|
256
|
+
let busy = false;
|
|
257
|
+
|
|
258
|
+
function hide() {
|
|
259
|
+
menu.style.display = 'none';
|
|
260
|
+
menu.innerHTML = '';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function show(hits) {
|
|
264
|
+
if (!hits || !hits.length) {
|
|
265
|
+
hide();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
menu.innerHTML = '';
|
|
269
|
+
hits.forEach((h) => {
|
|
270
|
+
const btn = document.createElement('button');
|
|
271
|
+
btn.type = 'button';
|
|
272
|
+
btn.className = 'onekite-autocomplete-item';
|
|
273
|
+
btn.textContent = h.displayName;
|
|
274
|
+
btn.style.display = 'block';
|
|
275
|
+
btn.style.width = '100%';
|
|
276
|
+
btn.style.textAlign = 'left';
|
|
277
|
+
btn.style.padding = '.35rem .5rem';
|
|
278
|
+
btn.style.border = '0';
|
|
279
|
+
btn.style.background = 'transparent';
|
|
280
|
+
btn.style.cursor = 'pointer';
|
|
281
|
+
btn.addEventListener('click', () => {
|
|
282
|
+
inputEl.value = h.displayName;
|
|
283
|
+
hide();
|
|
284
|
+
try { onPick && onPick(h); } catch (e) {}
|
|
285
|
+
});
|
|
286
|
+
btn.addEventListener('mouseenter', () => { btn.style.background = 'rgba(0,0,0,.05)'; });
|
|
287
|
+
btn.addEventListener('mouseleave', () => { btn.style.background = 'transparent'; });
|
|
288
|
+
menu.appendChild(btn);
|
|
289
|
+
});
|
|
290
|
+
menu.style.display = 'block';
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function run(q) {
|
|
294
|
+
if (busy) return;
|
|
295
|
+
busy = true;
|
|
296
|
+
try {
|
|
297
|
+
const hits = await searchAddresses(q, 6);
|
|
298
|
+
if (String(inputEl.value || '').trim() !== q) return; // ignore stale
|
|
299
|
+
show(hits);
|
|
300
|
+
} catch (e) {
|
|
301
|
+
hide();
|
|
302
|
+
} finally {
|
|
303
|
+
busy = false;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
inputEl.addEventListener('input', () => {
|
|
308
|
+
const q = String(inputEl.value || '').trim();
|
|
309
|
+
lastQuery = q;
|
|
310
|
+
if (timer) clearTimeout(timer);
|
|
311
|
+
if (q.length < 3) {
|
|
312
|
+
hide();
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
timer = setTimeout(() => run(lastQuery), 250);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
inputEl.addEventListener('focus', () => {
|
|
319
|
+
const q = String(inputEl.value || '').trim();
|
|
320
|
+
if (q.length >= 3) {
|
|
321
|
+
if (timer) clearTimeout(timer);
|
|
322
|
+
timer = setTimeout(() => run(q), 150);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
inputEl.addEventListener('keydown', (e) => {
|
|
327
|
+
if (e.key === 'Escape') hide();
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Close when clicking outside (once per menu)
|
|
331
|
+
document.addEventListener('click', (e) => {
|
|
332
|
+
try {
|
|
333
|
+
if (!anchor.contains(e.target)) hide();
|
|
334
|
+
} catch (err) {}
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
190
338
|
function formToObject(form) {
|
|
191
339
|
const out = {};
|
|
192
340
|
new FormData(form).forEach((v, k) => {
|
|
@@ -698,6 +846,19 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
698
846
|
} catch (e) {}
|
|
699
847
|
});
|
|
700
848
|
}
|
|
849
|
+
|
|
850
|
+
// Autocomplete like on the calendar validation modal
|
|
851
|
+
const addrInput = document.getElementById('onekite-pickup-address');
|
|
852
|
+
attachAddressAutocomplete(addrInput, (h) => {
|
|
853
|
+
try {
|
|
854
|
+
if (!h || !Number.isFinite(Number(h.lat)) || !Number.isFinite(Number(h.lon))) return;
|
|
855
|
+
const lat = Number(h.lat);
|
|
856
|
+
const lon = Number(h.lon);
|
|
857
|
+
marker.setLatLng([lat, lon]);
|
|
858
|
+
map.setView([lat, lon], 16);
|
|
859
|
+
setLatLon(lat, lon);
|
|
860
|
+
} catch (e) {}
|
|
861
|
+
});
|
|
701
862
|
setTimeout(() => { try { map.invalidateSize(); } catch (e) {} }, 250);
|
|
702
863
|
} catch (e) {}
|
|
703
864
|
});
|
|
@@ -887,6 +1048,14 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
887
1048
|
}
|
|
888
1049
|
});
|
|
889
1050
|
}
|
|
1051
|
+
|
|
1052
|
+
// Autocomplete like on the calendar validation modal
|
|
1053
|
+
attachAddressAutocomplete(addrInput, (h) => {
|
|
1054
|
+
try {
|
|
1055
|
+
if (!h || !Number.isFinite(Number(h.lat)) || !Number.isFinite(Number(h.lon))) return;
|
|
1056
|
+
setMarker(Number(h.lat), Number(h.lon), 16);
|
|
1057
|
+
} catch (e) {}
|
|
1058
|
+
});
|
|
890
1059
|
} catch (e) {
|
|
891
1060
|
// ignore
|
|
892
1061
|
}
|
package/public/client.js
CHANGED
|
@@ -638,10 +638,20 @@ function attachAddressAutocomplete(inputEl, onPick) {
|
|
|
638
638
|
if (inputEl.getAttribute('data-onekite-autocomplete') === '1') return;
|
|
639
639
|
inputEl.setAttribute('data-onekite-autocomplete', '1');
|
|
640
640
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
inputEl.
|
|
644
|
-
|
|
641
|
+
// In Bootstrap input-groups (especially in ACP), wrapping the input breaks layout.
|
|
642
|
+
// So we anchor the menu to the closest input-group (or parent) without moving the input.
|
|
643
|
+
const host = inputEl.closest && inputEl.closest('.input-group')
|
|
644
|
+
? inputEl.closest('.input-group')
|
|
645
|
+
: (inputEl.parentNode || document.body);
|
|
646
|
+
|
|
647
|
+
try {
|
|
648
|
+
const cs = window.getComputedStyle(host);
|
|
649
|
+
if (!cs || cs.position === 'static') {
|
|
650
|
+
host.style.position = 'relative';
|
|
651
|
+
}
|
|
652
|
+
} catch (e) {
|
|
653
|
+
host.style.position = 'relative';
|
|
654
|
+
}
|
|
645
655
|
|
|
646
656
|
const menu = document.createElement('div');
|
|
647
657
|
menu.className = 'onekite-autocomplete-menu';
|
|
@@ -657,7 +667,7 @@ function attachAddressAutocomplete(inputEl, onPick) {
|
|
|
657
667
|
menu.style.overflowY = 'auto';
|
|
658
668
|
menu.style.display = 'none';
|
|
659
669
|
menu.style.borderRadius = '0 0 .375rem .375rem';
|
|
660
|
-
|
|
670
|
+
host.appendChild(menu);
|
|
661
671
|
|
|
662
672
|
let timer = null;
|
|
663
673
|
let lastQuery = '';
|
|
@@ -735,7 +745,7 @@ function attachAddressAutocomplete(inputEl, onPick) {
|
|
|
735
745
|
// Close when clicking outside
|
|
736
746
|
document.addEventListener('click', (e) => {
|
|
737
747
|
try {
|
|
738
|
-
if (!
|
|
748
|
+
if (!host.contains(e.target)) hide();
|
|
739
749
|
} catch (err) {}
|
|
740
750
|
});
|
|
741
751
|
|