232 lines
7.5 KiB
Plaintext
232 lines
7.5 KiB
Plaintext
---
|
|
import ProposeModal from "../components/ProposeModal.astro";
|
|
import SiteNav from "../components/SiteNav.astro";
|
|
import LevelToggle from "../components/LevelToggle.astro";
|
|
import BuildStamp from "../components/BuildStamp.astro";
|
|
import "../styles/global.css";
|
|
|
|
const {
|
|
title,
|
|
editionLabel,
|
|
editionKey,
|
|
statusLabel,
|
|
statusKey,
|
|
level,
|
|
version
|
|
} = Astro.props;
|
|
|
|
const lvl = level ?? 1;
|
|
|
|
const canonical = Astro.site
|
|
? new URL(Astro.url.pathname, Astro.site).href
|
|
: Astro.url.href;
|
|
|
|
// Cible Gitea (injectée au build)
|
|
const GITEA_BASE = import.meta.env.PUBLIC_GITEA_BASE ?? "";
|
|
const GITEA_OWNER = import.meta.env.PUBLIC_GITEA_OWNER ?? "";
|
|
const GITEA_REPO = import.meta.env.PUBLIC_GITEA_REPO ?? "";
|
|
---
|
|
<!doctype html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>{title ? `${title} — Archicratie` : "Archicratie"}</title>
|
|
|
|
<link rel="canonical" href={canonical} />
|
|
<link rel="sitemap" href="/sitemap-index.xml" />
|
|
|
|
<meta data-pagefind-filter="edition[content]" content={String(editionKey ?? editionLabel)} />
|
|
<meta data-pagefind-filter="level[content]" content={String(lvl)} />
|
|
<meta data-pagefind-filter="status[content]" content={String(statusKey ?? statusLabel)} />
|
|
|
|
<meta data-pagefind-meta={`edition:${String(editionKey ?? editionLabel)}`} />
|
|
<meta data-pagefind-meta={`level:${String(lvl)}`} />
|
|
<meta data-pagefind-meta={`status:${String(statusKey ?? statusLabel)}`} />
|
|
<meta data-pagefind-meta={`version:${String(version ?? "")}`} />
|
|
</head>
|
|
|
|
<body data-doc-title={title} data-doc-version={version}>
|
|
<header>
|
|
<SiteNav />
|
|
<div class="edition-bar">
|
|
<span class="badge"><strong>Édition</strong> : {editionLabel}</span>
|
|
<span class="badge"><strong>Statut</strong> : {statusLabel}</span>
|
|
<span class="badge"><strong>Niveau</strong> : {lvl}</span>
|
|
<span class="badge"><strong>Version</strong> : {version}</span>
|
|
<LevelToggle initialLevel={lvl} />
|
|
</div>
|
|
</header>
|
|
|
|
<main>
|
|
<article class="reading" data-pagefind-body>
|
|
<slot />
|
|
<BuildStamp />
|
|
</article>
|
|
</main>
|
|
|
|
<ProposeModal />
|
|
|
|
<!-- IMPORTANT: define:vars injecte les constantes dans le JS sans templating fragile -->
|
|
<script is:inline define:vars={{ GITEA_BASE, GITEA_OWNER, GITEA_REPO }}>
|
|
(() => {
|
|
try {
|
|
// Nettoyage si un ancien bug a injecté ?body=... dans l'URL
|
|
if (window.location.search.includes("body=")) {
|
|
history.replaceState(null, "", window.location.pathname + window.location.hash);
|
|
}
|
|
|
|
const docTitle = document.body.dataset.docTitle || document.title;
|
|
const docVersion = document.body.dataset.docVersion || "";
|
|
|
|
const giteaReady = Boolean(GITEA_BASE && GITEA_OWNER && GITEA_REPO);
|
|
|
|
// Limites pragmatiques :
|
|
// - FULL_TEXT_SOFT_LIMIT : taille max du paragraphe qu'on essaie d'embarquer tel quel
|
|
// - URL_HARD_LIMIT : taille max de l'URL finale issues/new?... (au-delà, on repasse en extrait)
|
|
const FULL_TEXT_SOFT_LIMIT = 1600;
|
|
const URL_HARD_LIMIT = 6500;
|
|
|
|
const quoteBlock = (s) =>
|
|
String(s || "")
|
|
.split(/\r?\n/)
|
|
.map((l) => `> ${l}`.trimEnd())
|
|
.join("\n");
|
|
|
|
function buildIssueURL(anchorId, fullText, excerpt) {
|
|
const base = String(GITEA_BASE).replace(/\/+$/, "");
|
|
const issue = new URL(`${base}/${GITEA_OWNER}/${GITEA_REPO}/issues/new`);
|
|
|
|
// URL locale "propre" : on ignore totalement query-string
|
|
const local = new URL(window.location.href);
|
|
local.search = "";
|
|
local.hash = anchorId;
|
|
|
|
const path = local.pathname;
|
|
const issueTitle = `[Correction] ${anchorId} — ${docTitle}`;
|
|
|
|
const hasFull = Boolean(fullText && fullText.length);
|
|
const canTryFull = hasFull && fullText.length <= FULL_TEXT_SOFT_LIMIT;
|
|
|
|
const makeBody = (embedFull) => {
|
|
const header = [
|
|
`Chemin: ${path}`,
|
|
`URL locale: ${local.toString()}`,
|
|
`Ancre: #${anchorId}`,
|
|
`Version: ${docVersion || "(non renseignée)"}`,
|
|
`Type: type/correction`,
|
|
`State: state/recevable`,
|
|
``,
|
|
];
|
|
|
|
const texteActuel = embedFull
|
|
? [
|
|
`Texte actuel (copie exacte du paragraphe):`,
|
|
quoteBlock(fullText),
|
|
]
|
|
: [
|
|
`Texte actuel (extrait):`,
|
|
quoteBlock(excerpt || ""),
|
|
``,
|
|
`Note: paragraphe long → extrait (texte complet copié au clic si possible).`,
|
|
];
|
|
|
|
const footer = [
|
|
``,
|
|
`Proposition (remplacer par):`,
|
|
``,
|
|
`Justification:`,
|
|
``,
|
|
`---`,
|
|
`Note: issue générée depuis le site (pré-remplissage).`,
|
|
];
|
|
|
|
return header.concat(texteActuel, footer).join("\n");
|
|
};
|
|
|
|
// 1) On tente "texte complet"
|
|
issue.searchParams.set("title", issueTitle);
|
|
issue.searchParams.set("body", makeBody(Boolean(canTryFull)));
|
|
|
|
// 2) Si l'URL devient trop longue, on repasse en extrait
|
|
if (issue.toString().length > URL_HARD_LIMIT) {
|
|
issue.searchParams.set("body", makeBody(false));
|
|
}
|
|
|
|
return issue.toString();
|
|
}
|
|
|
|
// Contrat : uniquement les paragraphes citables
|
|
const paras = Array.from(document.querySelectorAll('.reading p[id^="p-"]'));
|
|
|
|
for (const p of paras) {
|
|
if (p.querySelector(".para-tools")) continue;
|
|
|
|
const tools = document.createElement("span");
|
|
tools.className = "para-tools";
|
|
|
|
const a = document.createElement("a");
|
|
a.className = "para-anchor";
|
|
a.href = `#${p.id}`;
|
|
a.setAttribute("aria-label", "Lien vers ce paragraphe");
|
|
a.textContent = "¶";
|
|
|
|
const citeBtn = document.createElement("button");
|
|
citeBtn.type = "button";
|
|
citeBtn.className = "para-cite";
|
|
citeBtn.textContent = "Citer";
|
|
|
|
citeBtn.addEventListener("click", async () => {
|
|
const pageUrl = new URL(window.location.href);
|
|
pageUrl.search = "";
|
|
pageUrl.hash = p.id;
|
|
|
|
const cite = `${docTitle}${docVersion ? ` (v${docVersion})` : ""} — ${pageUrl.toString()}`;
|
|
|
|
try {
|
|
await navigator.clipboard.writeText(cite);
|
|
const prev = citeBtn.textContent;
|
|
citeBtn.textContent = "Copié";
|
|
setTimeout(() => (citeBtn.textContent = prev), 900);
|
|
} catch {
|
|
window.prompt("Copiez la citation :", cite);
|
|
}
|
|
});
|
|
|
|
tools.appendChild(a);
|
|
tools.appendChild(citeBtn);
|
|
|
|
if (giteaReady) {
|
|
const propose = document.createElement("a");
|
|
propose.className = "para-propose";
|
|
propose.textContent = "Proposer";
|
|
propose.setAttribute("aria-label", "Proposer une correction sur Gitea");
|
|
propose.setAttribute("data-propose", "1");
|
|
|
|
const raw = (p.textContent || "").trim().replace(/\s+/g, " ");
|
|
const excerpt = raw.length > 420 ? (raw.slice(0, 420) + "…") : raw;
|
|
|
|
const issueUrl = buildIssueURL(p.id, raw, excerpt);
|
|
|
|
// progressive enhancement : sans JS/modal, href fonctionne.
|
|
propose.href = issueUrl;
|
|
|
|
// la modal lit data-url en priorité (garde aussi href).
|
|
propose.dataset.url = issueUrl;
|
|
|
|
// Option B : texte complet disponible au clic (presse-papier + upgrade)
|
|
propose.dataset.full = raw;
|
|
|
|
tools.appendChild(propose);
|
|
}
|
|
|
|
p.appendChild(tools);
|
|
}
|
|
} catch (err) {
|
|
console.error("[EditionLayout] para-tools init failed:", err);
|
|
}
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|