chore: track site sources + ignore local env/backups
This commit is contained in:
131
src/components/Term.astro
Normal file
131
src/components/Term.astro
Normal 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>
|
||||
Reference in New Issue
Block a user