feat: widget événements dynamiques depuis le calendrier Nextcloud
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
14cb68c4c5
commit
a580d98518
1 changed files with 86 additions and 3 deletions
|
|
@ -299,13 +299,13 @@
|
|||
<main>
|
||||
|
||||
<h2 class="section-title">Prochains événements</h2>
|
||||
<div class="events">
|
||||
<div class="event-card">
|
||||
<div class="events" id="events-container">
|
||||
<div class="event-card" id="static-reunion">
|
||||
<div class="event-date">1<sup>er</sup> & 3<sup>e</sup> jeudis</div>
|
||||
<div class="event-title">Réunion mensuelle</div>
|
||||
<div class="event-where">📍 Dynamo Chambéry — 18h00</div>
|
||||
</div>
|
||||
<div class="event-card">
|
||||
<div class="event-card" id="static-installparty">
|
||||
<div class="event-date">Voir le calendrier</div>
|
||||
<div class="event-title">Install Party & ateliers</div>
|
||||
<div class="event-where">📅 <a href="https://installparty.alpinux.org" style="color:#1a6bbf;">installparty.alpinux.org</a></div>
|
||||
|
|
@ -353,5 +353,88 @@
|
|||
</p>
|
||||
</footer>
|
||||
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const CAL_URL = '/public-calendars/n5BWPYsxw7FCYozM';
|
||||
|
||||
function unfold(ics) {
|
||||
return ics.replace(/\r\n[ \t]/g, '').replace(/\n[ \t]/g, '');
|
||||
}
|
||||
|
||||
function parseDate(line) {
|
||||
const ci = line.indexOf(':');
|
||||
const params = line.substring(0, ci).toUpperCase();
|
||||
const val = line.substring(ci + 1).trim();
|
||||
const y = +val.substring(0, 4), mo = +val.substring(4, 6) - 1, d = +val.substring(6, 8);
|
||||
if (params.includes('VALUE=DATE') || /^\d{8}$/.test(val))
|
||||
return { date: new Date(y, mo, d), allDay: true };
|
||||
const h = +val.substring(9, 11), mi = +val.substring(11, 13), s = +val.substring(13, 15);
|
||||
const dt = val.endsWith('Z')
|
||||
? new Date(Date.UTC(y, mo, d, h, mi, s))
|
||||
: new Date(y, mo, d, h, mi, s);
|
||||
return { date: dt, allDay: false };
|
||||
}
|
||||
|
||||
function parseICS(text) {
|
||||
const lines = unfold(text).split(/\r?\n/);
|
||||
const events = [], cap = /^(.+?)(;[^:]*)?:(.*)$/;
|
||||
let cur = null;
|
||||
for (const line of lines) {
|
||||
if (line === 'BEGIN:VEVENT') { cur = {}; continue; }
|
||||
if (line === 'END:VEVENT') { if (cur && cur.start) events.push(cur); cur = null; continue; }
|
||||
if (!cur) continue;
|
||||
const m = cap.exec(line);
|
||||
if (!m) continue;
|
||||
const key = m[1].toUpperCase();
|
||||
const val = m[3];
|
||||
if (key === 'SUMMARY') cur.title = val.replace(/\\,/g, ',').replace(/\\n/g, ' ');
|
||||
if (key === 'LOCATION') cur.location = val.replace(/\\,/g, ',');
|
||||
if (key === 'DTSTART') cur.start = parseDate(line);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
function fmtDate(d, allDay) {
|
||||
const opts = { weekday: 'long', day: 'numeric', month: 'long' };
|
||||
if (!allDay) opts.hour = '2-digit', opts.minute = '2-digit';
|
||||
return d.toLocaleDateString('fr-FR', opts);
|
||||
}
|
||||
|
||||
function makeCard(ev) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'event-card';
|
||||
card.innerHTML =
|
||||
'<div class="event-date">' + fmtDate(ev.start.date, ev.start.allDay) + '</div>' +
|
||||
'<div class="event-title">' + ev.title + '</div>' +
|
||||
(ev.location ? '<div class="event-where">📍 ' + ev.location + '</div>' : '');
|
||||
return card;
|
||||
}
|
||||
|
||||
async function load() {
|
||||
try {
|
||||
const r = await fetch(CAL_URL);
|
||||
if (!r.ok) return;
|
||||
const text = await r.text();
|
||||
if (!text.trim()) return;
|
||||
const now = new Date();
|
||||
now.setHours(0, 0, 0, 0);
|
||||
const upcoming = parseICS(text)
|
||||
.filter(e => e.start.date >= now)
|
||||
.sort((a, b) => a.start.date - b.start.date)
|
||||
.slice(0, 6);
|
||||
if (!upcoming.length) return;
|
||||
|
||||
const container = document.getElementById('events-container');
|
||||
const anchor = document.getElementById('static-installparty');
|
||||
const staticReunion = document.getElementById('static-reunion');
|
||||
if (staticReunion) staticReunion.remove();
|
||||
upcoming.forEach(ev => container.insertBefore(makeCard(ev), anchor));
|
||||
} catch (_) { /* silently keep static tiles */ }
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', load);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Reference in a new issue