diff --git a/extension/liste.css b/extension/liste.css index 580fb26..8eaf117 100644 --- a/extension/liste.css +++ b/extension/liste.css @@ -59,6 +59,28 @@ h1 { cursor: pointer; } +.evenement-publication { + flex-shrink: 0; + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 0.25rem; +} + +.evenement-publie { + align-self: flex-end; + padding: 0.15rem 0.5rem; + border-radius: 0.75rem; + background: #1a7f37; + color: #fff; + font-size: 0.8rem; + font-weight: 600; +} + +.evenement-lien-mairie { + font-size: 0.85rem; +} + #etat { margin-bottom: 1.5rem; } diff --git a/extension/liste.js b/extension/liste.js index cc01247..ece5d44 100644 --- a/extension/liste.js +++ b/extension/liste.js @@ -2,6 +2,8 @@ import { recupererFluxCalendrier } from "./nextcloud.js"; import { parserEvenements } from "./evenements.js"; import { formaterDate, formaterLieu, trierParDebut } from "./presentation.js"; import { deposerEvenement } from "./transfert.js"; +import { calculerPeriode, extraireCartes, recupererAgendaPublie } from "./agenda-mairie.js"; +import { indexerCartes, trouverPublication } from "./publication.js"; const URL_NEXTCLOUD = "https://atelier-huit.frama.space/"; @@ -49,6 +51,8 @@ function rendreEvenements(evenements) { function rendreEvenement(evenement) { const item = document.createElement("li"); item.className = "evenement"; + // Ancre pour retrouver le
  • lors de l'annotation « publié » (phase 2). + item.dataset.uid = evenement.uid; const infos = document.createElement("div"); infos.className = "evenement-infos"; @@ -135,10 +139,63 @@ async function charger() { // « Illegal invocation » dans le navigateur. const res = await recupererFluxCalendrier((u, o) => fetch(u, o)); if (res.ok) { - rendreEvenements(trierParDebut(parserEvenements(res.flux, new Date()))); + const evenements = trierParDebut(parserEvenements(res.flux, new Date())); + rendreEvenements(evenements); + // Enrichissement additif : la liste est déjà rendue, l'agenda ne la + // bloque jamais. Un échec de scraping laisse la liste intacte, sans badge. + // Fire-and-forget : le `.catch` garantit qu'aucune erreur imprévue + // (indexation, annotation) ne devienne une rejection non gérée. + annoterPublications(evenements).catch((erreur) => + console.error("Écho du Huit : annotation des publications interrompue.", erreur), + ); } else { rendreErreur(res.erreur); } } +// Phase 2 : marque « publié » les événements retrouvés sur l'agenda mairie et y +// pose leur lien direct. Dégradation gracieuse : agenda injoignable ou structure +// changée → on ne touche à rien (aucun faux positif, biais précision v1). +async function annoterPublications(evenements) { + if (evenements.length === 0) return; + + const periode = calculerPeriode(evenements, new Date()); + // Agenda public : fetch SANS credentials. + const res = await recupererAgendaPublie((u, o) => fetch(u, o), extraireCartes, periode); + if (!res.ok) return; + + const index = indexerCartes(res.cartes); + for (const evenement of evenements) { + const publication = trouverPublication(evenement, index); + if (!publication.publie) continue; + + const item = liste.querySelector(`[data-uid="${CSS.escape(evenement.uid)}"]`); + if (item) annoterItem(item, publication.lien); + } +} + +// Badge « Publié » + lien direct ; le bouton « Créer » est retiré (anti-doublon : +// un événement déjà publié n'a pas à être re-soumis). textContent uniquement, +// le lien vient d'une source externe. +function annoterItem(item, lien) { + item.querySelector(".evenement-creer")?.remove(); + + const publication = document.createElement("div"); + publication.className = "evenement-publication"; + + const badge = document.createElement("span"); + badge.className = "evenement-publie"; + badge.textContent = "Publié"; + + const voir = document.createElement("a"); + voir.className = "evenement-lien-mairie"; + voir.href = lien; + voir.target = "_blank"; + voir.rel = "noopener"; + voir.textContent = "Voir sur le site mairie"; + + publication.append(badge, voir); + item.append(publication); +} + charger();