The mairie form needs an organiser, an email and the café defaults. Ship the defaults in the repo and let each volunteer override the personal bits in config.local.json (gitignored, optional: a fresh clone still works). config.js carries neither import nor export on purpose, so that it loads both as a classic script (content script, background) and as an ESM module. That is what lets presentation.js drop its duplicated café address. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
88 lines
3.5 KiB
JavaScript
88 lines
3.5 KiB
JavaScript
// Présentation des Event pour la liste : ordre, formatage date, défaut lieu.
|
|
// Fonctions pures, sans DOM (testables). Locale et fuseau sont des constantes :
|
|
// l'appli est mono-locale (bénévoles de Cugnaux).
|
|
|
|
// config.js n'exporte rien : il pose globalThis.EchoConfig (cf. DECISIONS.md
|
|
// 2026-07-17). L'import ne sert qu'à son effet de bord.
|
|
import "./config.js";
|
|
|
|
const LOCALE = "fr-FR";
|
|
const FUSEAU = "Europe/Paris";
|
|
|
|
// Source unique : le formulaire mairie a besoin de la même adresse (et de ses
|
|
// coordonnées) que la liste.
|
|
export const LIEU_PAR_DEFAUT = globalThis.EchoConfig.DEFAUTS.cafe.adresse;
|
|
|
|
// Un événement horodaté porte un vrai instant : on l'affiche à l'heure de
|
|
// Cugnaux, quel que soit le fuseau de la machine.
|
|
const FORMAT_JOUR_PARIS = new Intl.DateTimeFormat(LOCALE, {
|
|
timeZone: FUSEAU,
|
|
dateStyle: "full",
|
|
});
|
|
const FORMAT_HEURE_PARIS = new Intl.DateTimeFormat(LOCALE, {
|
|
timeZone: FUSEAU,
|
|
timeStyle: "short",
|
|
});
|
|
|
|
// Sans timeZone, DÉLIBÉRÉMENT (ne pas « rétablir » FUSEAU ici) : une journée
|
|
// entière est une date *flottante* (RFC 5545), sans fuseau. ical.js la
|
|
// matérialise à minuit local ; la relire en local restitue le bon jour partout,
|
|
// alors que la convertir vers Paris la ferait glisser au 9 depuis Tokyo.
|
|
const FORMAT_JOUR_FLOTTANT = new Intl.DateTimeFormat(LOCALE, {
|
|
dateStyle: "full",
|
|
});
|
|
|
|
// Le parseur rend l'ordre du flux : l'ordre chronologique se décide ici.
|
|
export function trierParDebut(evenements) {
|
|
return [...evenements].sort((a, b) => a.debut.getTime() - b.debut.getTime());
|
|
}
|
|
|
|
// Une journée entière n'a pas d'heure : afficher « 00:00 » serait un mensonge.
|
|
export function formaterDate(evenement) {
|
|
return evenement.journeeEntiere
|
|
? formaterJourneeEntiere(evenement)
|
|
: formaterHorodate(evenement);
|
|
}
|
|
|
|
function formaterJourneeEntiere({ debut, fin }) {
|
|
const premierJour = FORMAT_JOUR_FLOTTANT.format(debut);
|
|
const dernierJour = veilleDe(fin);
|
|
|
|
// Un seul jour, ou pas de DTEND exploitable (le parseur pose alors fin === debut).
|
|
if (dernierJour.getTime() <= debut.getTime()) return premierJour;
|
|
|
|
return `du ${premierJour} au ${FORMAT_JOUR_FLOTTANT.format(dernierJour)}`;
|
|
}
|
|
|
|
// DTEND est EXCLUSIF (RFC 5545) : un événement du 18 porte DTEND=19, un festival
|
|
// du 18 au 20 porte DTEND=21. Sans ce recul d'un jour, on annoncerait un jour de
|
|
// trop — une date FAUSSE, pas seulement laide.
|
|
// Composantes locales (jamais « -24h ») : la date est flottante, et un
|
|
// changement d'heure ferait basculer le jour.
|
|
function veilleDe(date) {
|
|
return new Date(date.getFullYear(), date.getMonth(), date.getDate() - 1);
|
|
}
|
|
|
|
function formaterHorodate({ debut, fin }) {
|
|
const jourDebut = FORMAT_JOUR_PARIS.format(debut);
|
|
const heureDebut = FORMAT_HEURE_PARIS.format(debut);
|
|
|
|
// DTEND absent : le parseur pose fin === debut. « de 10:30 à 10:30 » serait absurde.
|
|
if (fin.getTime() <= debut.getTime()) return `${jourDebut} à ${heureDebut}`;
|
|
|
|
const jourFin = FORMAT_JOUR_PARIS.format(fin);
|
|
const heureFin = FORMAT_HEURE_PARIS.format(fin);
|
|
|
|
// Comparaison des jours tels qu'AFFICHÉS (donc à Paris) : un événement de
|
|
// 22:00 à 02:00 change de jour, l'annoncer sur un seul serait un mensonge.
|
|
if (jourFin === jourDebut) return `${jourDebut} de ${heureDebut} à ${heureFin}`;
|
|
|
|
return `du ${jourDebut} à ${heureDebut} au ${jourFin} à ${heureFin}`;
|
|
}
|
|
|
|
// La plupart des événements se tiennent au café et n'ont pas de LOCATION :
|
|
// afficher l'adresse par défaut plutôt qu'une ligne vide.
|
|
export function formaterLieu(evenement) {
|
|
return evenement.lieu.trim() || LIEU_PAR_DEFAUT;
|
|
}
|