Compare commits

..

11 Commits

5 changed files with 485 additions and 35 deletions
+212
View File
@@ -0,0 +1,212 @@
(() => {
const OWNER = "You-re-like-Windows-a-bitch";
const REPO = "ISO";
const RELEASES_URL = `https://api.github.com/repos/${OWNER}/${REPO}/releases?per_page=6`;
const TAGS_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=6`;
const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
const currentVersionNode = document.querySelector("[data-current-version]");
const currentVersionDateNode = document.querySelector("[data-current-version-date]");
const currentReleaseNoteNode = document.querySelector("[data-current-release-note]");
const latestDownloadNode = document.querySelector("[data-latest-download]");
const viewMoreNode = document.querySelector("[data-view-more]");
const versionsListNode = document.querySelector("[data-versions-list]");
function formatDate(value) {
if (!value) return "";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return "";
return date.toLocaleDateString("fr-FR", {
year: "numeric",
month: "long",
day: "numeric",
});
}
function getReleaseLabel(entry) {
return entry?.name || entry?.tag_name || entry?.ref || "Sans nom";
}
function getReleasePageUrl(entry, mode) {
if (mode === "releases") {
return entry?.html_url || REPO_URL;
}
if (entry?.name) {
return `${REPO_URL}/releases/tag/${encodeURIComponent(entry.name)}`;
}
return REPO_URL;
}
function sanitizeReleaseHtml(html) {
const wrapper = document.createElement("div");
wrapper.innerHTML = html || "";
wrapper.querySelectorAll("script, iframe, object, embed, link, style").forEach((node) => node.remove());
wrapper.querySelectorAll("*").forEach((node) => {
Array.from(node.attributes).forEach((attribute) => {
const name = attribute.name.toLowerCase();
const value = attribute.value || "";
if (name.startsWith("on")) {
node.removeAttribute(attribute.name);
return;
}
if ((name === "href" || name === "src") && /^javascript:/i.test(value)) {
node.removeAttribute(attribute.name);
}
});
});
return wrapper.innerHTML;
}
function pickDirectDownloadUrl(release) {
const assets = Array.isArray(release?.assets) ? release.assets : [];
const isoAsset = assets.find((asset) => /\.iso$/i.test(asset?.name || ""));
const directAsset = isoAsset || assets[0];
if (directAsset) {
return directAsset.browser_download_url || directAsset.url || release.html_url || REPO_URL;
}
return release?.zipball_url || release?.html_url || REPO_URL;
}
function renderEmptyState(title, message) {
if (!versionsListNode) return;
versionsListNode.innerHTML = `
<li class="version-item version-item-empty">
<div class="version-main">
<strong>${title}</strong>
<span>${message}</span>
</div>
<div class="version-side">
<a class="button button-secondary" href="${REPO_URL}" target="_blank" rel="noreferrer">Voir le repo</a>
</div>
</li>
`;
}
function renderList(items, mode) {
if (!versionsListNode) return;
versionsListNode.innerHTML = "";
const visibleItems = items.slice(1, 6);
if (!visibleItems.length) {
if (items.length === 0) {
renderEmptyState("Aucune release trouvée", "Le dépôt n'expose pas encore de release ou les releases ne sont pas accessibles.");
} else {
renderEmptyState(
mode === "releases" ? "Aucune release précédente trouvée" : "Aucun tag précédent trouvé",
mode === "releases"
? "Il n'y a qu'une seule release publiée pour le moment."
: "Il n'y a qu'un seul tag disponible pour le moment."
);
}
return;
}
visibleItems.forEach((entry) => {
const li = document.createElement("li");
li.className = "version-item";
const label = getReleaseLabel(entry);
const downloadUrl = getReleasePageUrl(entry, mode);
const published = formatDate(entry.published_at || entry.commit?.committer?.date);
li.innerHTML = `
<div class="version-main">
<strong>${label}</strong>
<span>${published ? `Publié le ${published}` : mode === "releases" ? "Date inconnue" : "Tag GitHub"}</span>
</div>
<div class="version-side">
<a class="button button-secondary" href="${downloadUrl}" target="_blank" rel="noreferrer">Télécharger</a>
</div>
`;
versionsListNode.appendChild(li);
});
}
async function fetchJson(url) {
const response = await fetch(url, {
headers: { Accept: "application/vnd.github+json" },
});
if (!response.ok) {
throw new Error(`GitHub API ${response.status}`);
}
return response.json();
}
async function loadReleases() {
if (currentVersionNode) currentVersionNode.textContent = "Chargement...";
if (currentVersionDateNode) currentVersionDateNode.textContent = "";
if (currentReleaseNoteNode) currentReleaseNoteNode.textContent = "";
if (latestDownloadNode) latestDownloadNode.href = REPO_URL;
if (viewMoreNode) viewMoreNode.href = `${REPO_URL}/releases`;
try {
const releases = await fetchJson(RELEASES_URL);
if (Array.isArray(releases) && releases.length > 0) {
const latest = releases[0];
const latestLabel = getReleaseLabel(latest);
const latestDownloadUrl = pickDirectDownloadUrl(latest);
const latestDate = formatDate(latest.published_at);
if (currentVersionNode) currentVersionNode.textContent = latestLabel;
if (currentVersionDateNode) currentVersionDateNode.textContent = latestDate ? ` · ${latestDate}` : "";
if (currentReleaseNoteNode) {
const bodyHtml = sanitizeReleaseHtml(latest.body || "");
currentReleaseNoteNode.innerHTML = bodyHtml || "Release GitHub la plus récente.";
}
if (latestDownloadNode) latestDownloadNode.href = latest.html_url || REPO_URL;
renderList(releases, "releases");
return;
}
} catch (error) {
// On tente le fallback tags ci-dessous.
}
try {
const tags = await fetchJson(TAGS_URL);
if (Array.isArray(tags) && tags.length > 0) {
const latestTag = tags[0];
const latestLabel = getReleaseLabel(latestTag);
const latestDownloadUrl = `${REPO_URL}/releases/tag/${encodeURIComponent(latestTag.name)}`;
if (currentVersionNode) currentVersionNode.textContent = `${latestLabel} (tag)`;
if (currentVersionDateNode) currentVersionDateNode.textContent = "";
if (currentReleaseNoteNode) {
currentReleaseNoteNode.textContent = "Aucune release GitHub publiée pour le moment, affichage des tags du dépôt.";
}
if (latestDownloadNode) latestDownloadNode.href = latestDownloadUrl;
renderList(tags, "tags");
return;
}
if (currentVersionNode) currentVersionNode.textContent = "Aucune release trouvée";
if (currentReleaseNoteNode) {
currentReleaseNoteNode.textContent = "Le dépôt ne renvoie ni release ni tag public.";
}
renderEmptyState("Aucune release trouvée", "Le dépôt ne renvoie ni release ni tag public.");
} catch (error) {
if (currentVersionNode) currentVersionNode.textContent = "Impossible de charger les releases";
if (currentReleaseNoteNode) {
currentReleaseNoteNode.textContent = `Erreur: ${error.message}. Vérifie que le repo GitHub est public et qu'il expose des releases ou des tags.`;
}
renderEmptyState("Impossible de charger les releases", "Ouvre le repo GitHub pour vérifier les releases ou les tags.");
}
}
loadReleases();
})();
+114
View File
@@ -0,0 +1,114 @@
.versions-panel {
margin-top: 1rem;
padding: 1rem;
border-radius: 1rem;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(148, 163, 184, 0.08);
}
.latest-version h2 {
margin: 0 0 0.35rem;
}
.latest-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
}
.latest-meta {
color: var(--muted);
font-size: 1.05rem;
}
.latest-actions {
display: flex;
gap: 0.6rem;
flex-wrap: wrap;
}
.latest-note {
margin: 0.85rem 0 0;
color: var(--muted);
line-height: 1.6;
}
.latest-note img {
display: block;
max-width: 100%;
height: auto;
margin: 0.75rem 0;
border-radius: 0.65rem;
}
.latest-note a {
color: #7dd3fc;
text-decoration: none;
}
.latest-note a:hover {
text-decoration: underline;
}
.versions-archive {
margin-top: 1rem;
}
.versions-list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 0.75rem;
}
.version-item {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
padding: 0.8rem 0.9rem;
border-radius: 0.8rem;
background: rgba(2, 6, 23, 0.24);
border: 1px solid rgba(148, 163, 184, 0.08);
}
.version-item-empty {
opacity: 0.8;
}
.version-main {
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.version-main a {
color: #7dd3fc;
text-decoration: none;
width: fit-content;
}
.version-main a:hover {
text-decoration: underline;
}
.version-side {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
@media (max-width: 880px) {
.latest-row,
.version-item {
flex-direction: column;
align-items: flex-start;
}
.version-side {
width: 100%;
}
}
+66 -21
View File
@@ -158,27 +158,6 @@ main[data-page-content] {
} }
.button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.8rem 1.2rem;
border-radius: 999px;
text-decoration: none;
font-weight: 700;
border: 1px solid transparent;
}
.button-primary {
background: linear-gradient(145deg, var(--accent), var(--accent-strong));
color: #08101d;
}
.button-secondary {
background: rgba(255,255,255,0.03);
color: var(--text);
}
.features { .features {
margin-top: 1.5rem; margin-top: 1.5rem;
@@ -259,6 +238,72 @@ main[data-page-content] {
line-height: 1.65; line-height: 1.65;
} }
/* Styles page Téléchargements */
.downloads {
margin-top: 1.5rem;
}
.downloads-header h1 { margin: 0 0 0.35rem; }
.download-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
margin-top: 1rem;
}
.download-card {
display: flex;
gap: 1rem;
align-items: stretch;
padding: 0.8rem;
border-radius: 0.75rem;
background: rgba(255,255,255,0.02);
border: 1px solid rgba(148,163,184,0.06);
}
.download-media { width: 220px; flex: 0 0 220px; overflow: hidden; border-radius: 0.5rem; }
.download-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.download-body { flex: 1 1 auto; display: flex; flex-direction: column; gap: 0.6rem; }
.download-actions { display: flex; gap: 0.6rem; margin-top: auto; }
@media (max-width: 880px) {
.download-grid { grid-template-columns: 1fr; }
.download-media { width: 140px; flex-basis: 140px; }
}
/* Versions toolbar */
.version-toolbar {
display: flex;
gap: 1rem;
align-items: center;
justify-content: space-between;
margin-top: 0.8rem;
}
.version-current { color: var(--muted); }
.versions-archive {
margin-top: 0.8rem;
padding: 0.8rem;
border-radius: 0.6rem;
background: rgba(255,255,255,0.01);
border: 1px solid rgba(148,163,184,0.06);
}
.versions-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.6rem; }
.version-item { display: flex; justify-content: space-between; align-items: center; padding: 0.4rem 0.6rem; border-radius: 0.45rem; }
.version-meta { color: var(--muted); margin-left: 0.6rem; font-size: 0.92rem; }
.version-actions { display: flex; gap: 0.4rem; }
@media (max-width: 640px) {
.version-toolbar { flex-direction: column; align-items: flex-start; }
.version-actions { width: 100%; display: flex; gap: 0.5rem; }
}
.features .card { .features .card {
flex: 1 1 0; flex: 1 1 0;
min-width: 0; min-width: 0;
+63
View File
@@ -274,6 +274,69 @@ body::after {
color: var(--text); color: var(--text);
} }
.button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.8rem 1.2rem;
border-radius: 999px;
text-decoration: none;
font-weight: 700;
border: 1px solid transparent;
cursor: pointer;
transition:
transform 180ms ease,
background-color 180ms ease,
border-color 180ms ease,
color 180ms ease,
box-shadow 180ms ease,
opacity 180ms ease;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.button-primary {
background: linear-gradient(145deg, var(--accent), var(--accent-strong));
color: #08101d;
box-shadow: 0 12px 28px rgba(2, 6, 23, 0.25);
}
.button-secondary {
background: rgba(255,255,255,0.04);
color: var(--text);
border-color: rgba(148, 163, 184, 0.18);
}
.button:hover,
.button:focus-visible {
transform: translateY(-1px);
box-shadow: 0 14px 30px rgba(2, 6, 23, 0.28);
}
.button-primary:hover,
.button-primary:focus-visible {
background: linear-gradient(145deg, color-mix(in srgb, var(--accent) 86%, white), color-mix(in srgb, var(--accent-strong) 92%, white));
}
.button-secondary:hover,
.button-secondary:focus-visible {
background: rgba(125, 211, 252, 0.12);
border-color: rgba(125, 211, 252, 0.35);
}
.button:focus-visible {
outline: 2px solid rgba(125, 211, 252, 0.85);
outline-offset: 2px;
}
.button[aria-disabled="true"],
.button:disabled {
opacity: 0.55;
pointer-events: none;
transform: none;
box-shadow: none;
}
@media (max-width: 900px) { @media (max-width: 900px) {
.grid { .grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
+29 -13
View File
@@ -3,24 +3,40 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>DL</title> <title>Téléchargements — FWS Linux</title>
<link rel="stylesheet" href="../assets/styles/downloads.css" />
</head> </head>
<body> <body>
<main data-page-content> <main data-page-content>
<section class="hero"> <section class="downloads">
<p class="eyebrow">Route DL</p> <header class="downloads-header">
<h1>Le contenu de la section DL charge ici.</h1> <h1>Téléchargements</h1>
<p class="lead"> <div class="versions-panel">
Cette page vit dans <span>p/dl/index.html</span> et son contenu est injecté dans le <div class="latest-version">
<span>main</span> quand l'URL pointe vers <span>/?p/dl/</span>. <h2>Dernière version</h2>
</p> <div class="latest-row">
<div class="hero-actions"> <div class="latest-meta">
<a class="button button-primary" href="/" data-route="home">Retour à l'accueil</a> <strong data-current-version>Chargement...</strong>
<a class="button button-secondary" href="/?p/dl/" data-route="dl">Recharger DL</a> <span data-current-version-date></span>
</div> </div>
<div class="latest-actions">
<a class="button" data-latest-download href="#" target="_blank" rel="noreferrer">Télécharger</a>
</div>
</div>
<p class="latest-note" data-current-release-note></p>
</div>
<div class="versions-archive">
<h3>5 versions précédentes</h3>
<ul class="versions-list" data-versions-list></ul>
</div>
</div>
</header>
</section> </section>
</main> </main>
<script src="../assets/js/downloads.js"></script>
</body> </body>
</html> </html>