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();