feat: widget événements dynamiques depuis le calendrier Nextcloud

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Cédrix 2026-05-03 10:05:34 +02:00
parent 14cb68c4c5
commit a580d98518

View file

@ -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> &amp; 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 &amp; 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>