chore: track site sources + ignore local env/backups

This commit is contained in:
2026-01-19 11:46:39 +01:00
parent aece8c5526
commit 5eb23a3de4
50 changed files with 3158 additions and 38 deletions

131
src/components/Term.astro Normal file
View File

@@ -0,0 +1,131 @@
---
const { term, slug } = Astro.props;
const href = slug ? `/glossaire/${slug}/` : "/glossaire/";
---
<a class="term" href={href} data-term={term} data-term-slug={slug}>
<slot>{term}</slot>
</a>
<script is:inline>
(() => {
// Popover léger, zéro dépendance, index statique.
const API = "/api/glossaire.json";
const CACHE_KEY = "__archicratieGlossaryIndex";
const CACHE_TIME_KEY = "__archicratieGlossaryIndexTime";
const TTL_MS = 5 * 60 * 1000;
function now() { return Date.now(); }
async function getIndex() {
try {
const t = Number(sessionStorage.getItem(CACHE_TIME_KEY));
const cached = sessionStorage.getItem(CACHE_KEY);
if (cached && t && (now() - t) < TTL_MS) return JSON.parse(cached);
const res = await fetch(API);
if (!res.ok) return null;
const data = await res.json();
sessionStorage.setItem(CACHE_KEY, JSON.stringify(data));
sessionStorage.setItem(CACHE_TIME_KEY, String(now()));
return data;
} catch {
return null;
}
}
function findEntry(index, slug, term) {
if (!index) return null;
if (slug) {
const bySlug = index.find((e) => e.slug === slug);
if (bySlug) return bySlug;
}
const lc = (term || "").toLowerCase();
return index.find((e) =>
e.term.toLowerCase() === lc ||
(e.aliases || []).some((a) => String(a).toLowerCase() === lc)
) || null;
}
let pop;
function ensurePopover() {
if (pop) return pop;
pop = document.createElement("div");
pop.className = "term-popover";
pop.style.position = "absolute";
pop.style.zIndex = "9999";
pop.style.maxWidth = "320px";
pop.style.padding = "10px 12px";
pop.style.border = "1px solid rgba(127,127,127,0.45)";
pop.style.borderRadius = "12px";
pop.style.background = "Canvas";
pop.style.boxShadow = "0 10px 30px rgba(0,0,0,0.15)";
pop.style.display = "none";
document.body.appendChild(pop);
document.addEventListener("click", (e) => {
if (!pop) return;
const target = e.target;
if (target && target.closest && target.closest(".term")) return;
pop.style.display = "none";
});
return pop;
}
function placePopover(anchor, node) {
const r = anchor.getBoundingClientRect();
const top = window.scrollY + r.bottom + 8;
const left = window.scrollX + Math.min(r.left, window.innerWidth - 340);
node.style.top = `${top}px`;
node.style.left = `${left}px`;
}
async function show(anchor) {
const slug = anchor.getAttribute("data-term-slug");
const term = anchor.getAttribute("data-term");
const index = await getIndex();
const entry = findEntry(index, slug, term);
const node = ensurePopover();
if (!entry) {
node.innerHTML = `<strong>${term || "Terme"}</strong><div style="margin-top:6px;opacity:0.85;">Voir la page du glossaire.</div>`;
} else {
node.innerHTML = `
<div style="display:flex;justify-content:space-between;gap:10px;align-items:baseline;">
<strong>${entry.term}</strong>
<a href="${entry.href}" style="font-size:12px;opacity:0.85;">ouvrir</a>
</div>
<div style="margin-top:6px;opacity:0.95;">${entry.definitionShort}</div>
`;
}
placePopover(anchor, node);
node.style.display = "block";
}
// Délégation d'événements : un seul handler pour tout le document
if (!window.__archicratieTermPopoverBound) {
window.__archicratieTermPopoverBound = true;
document.addEventListener("mouseover", (e) => {
const a = e.target && e.target.closest ? e.target.closest(".term") : null;
if (!a) return;
// éviter popover si pas de slug (cas rares)
show(a);
});
document.addEventListener("focusin", (e) => {
const a = e.target && e.target.closest ? e.target.closest(".term") : null;
if (!a) return;
show(a);
});
}
})();
</script>
<style>
.term {
text-decoration: underline;
text-underline-offset: 3px;
cursor: help;
}
</style>