@hgdc/docs 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/docs.schema.json +21 -0
  2. package/package.json +3 -1
  3. package/src/build/auto-import.ts +5 -24
  4. package/src/core/components.ts +166 -0
  5. package/src/core/config.ts +42 -0
  6. package/src/core/feedback-api.ts +17 -0
  7. package/src/core/validate.ts +276 -0
  8. package/src/integration.ts +29 -15
  9. package/src/overrides/Footer.astro +154 -0
  10. package/src/overrides/Head.astro +14 -14
  11. package/src/overrides/PageTitle.astro +95 -95
  12. package/src/overrides/Sidebar.astro +60 -60
  13. package/src/overrides/SocialIcons.astro +59 -0
  14. package/template/.astro/content-assets.mjs +4 -1
  15. package/template/.astro/content-modules.mjs +15 -15
  16. package/template/.build/.well-known/llms.txt +15 -15
  17. package/template/.build/404.html +5 -5
  18. package/template/.build/_astro/Search.astro_astro_type_script_index_0_lang.g9hoxVJg.js +1 -0
  19. package/template/.build/_astro/common.CQYUsl_5.css +3 -0
  20. package/template/.build/_astro/{ui-core.CrG20ya_.js → ui-core.Clh5jUUO.js} +1 -1
  21. package/template/.build/_hgdc/corpus.json +1 -1
  22. package/template/.build/demarrer/premiere-doc/index.html +15 -15
  23. package/template/.build/demarrer/premiere-doc.md +3 -3
  24. package/template/.build/explications/donnees/index.html +10 -10
  25. package/template/.build/explications/fonctionnement/index.html +10 -11
  26. package/template/.build/guides/agents/index.html +11 -11
  27. package/template/.build/guides/agents.md +1 -1
  28. package/template/.build/guides/assistant/index.html +11 -11
  29. package/template/.build/guides/composants/index.html +19 -19
  30. package/template/.build/guides/composants.md +10 -8
  31. package/template/.build/guides/ecrire/index.html +12 -13
  32. package/template/.build/guides/elements/index.html +1 -0
  33. package/template/.build/guides/navigation/index.html +12 -13
  34. package/template/.build/guides/publier/index.html +14 -23
  35. package/template/.build/guides/publier.md +1 -1
  36. package/template/.build/guides/theme/index.html +12 -13
  37. package/template/.build/index.html +11 -12
  38. package/template/.build/index.md +9 -9
  39. package/template/.build/llms-full.txt +59 -63
  40. package/template/.build/llms.txt +15 -15
  41. package/template/.build/pagefind/fragment/fr-ch_148313b.pf_fragment +0 -0
  42. package/template/.build/pagefind/fragment/fr-ch_35f556a.pf_fragment +0 -0
  43. package/template/.build/pagefind/fragment/fr-ch_48ca71e.pf_fragment +0 -0
  44. package/template/.build/pagefind/fragment/fr-ch_c589c2a.pf_fragment +0 -0
  45. package/template/.build/pagefind/index/fr-ch_c5ced52.pf_index +0 -0
  46. package/template/.build/pagefind/pagefind-entry.json +1 -1
  47. package/template/.build/pagefind/pagefind.fr-ch_8a246aa922758.pf_meta +0 -0
  48. package/template/.build/reference/api-assistant/index.html +12 -12
  49. package/template/.build/reference/api-assistant.md +2 -12
  50. package/template/.build/reference/cli/index.html +10 -10
  51. package/template/.build/reference/docs-json/index.html +35 -11
  52. package/template/.build/reference/docs-json.md +18 -14
  53. package/template/.build/reference/mcp/index.html +11 -11
  54. package/template/node_modules/.vite/deps/_metadata.json +8 -8
  55. package/template/.build/_astro/Search.astro_astro_type_script_index_0_lang.Cn3-p701.js +0 -1
  56. package/template/.build/_astro/common.C-MpX45O.css +0 -3
  57. package/template/.build/pagefind/fragment/fr-ch_68e734b.pf_fragment +0 -0
  58. package/template/.build/pagefind/fragment/fr-ch_cfa4336.pf_fragment +0 -0
  59. package/template/.build/pagefind/fragment/fr-ch_e02aa49.pf_fragment +0 -0
  60. package/template/.build/pagefind/fragment/fr-ch_e254a54.pf_fragment +0 -0
  61. package/template/.build/pagefind/index/fr-ch_a71411b.pf_index +0 -0
  62. package/template/.build/pagefind/pagefind.fr-ch_2f3ea8fccf961.pf_meta +0 -0
@@ -10,7 +10,7 @@ import { remarkAutoImport } from "./build/auto-import.ts"
10
10
  import { remarkBaseLinks } from "./build/base-links.ts"
11
11
  import { publicFiles } from "./build/public-files.ts"
12
12
  import { writeMachineReadable } from "./build/outputs.ts"
13
- import { parseDocsConfig, withHostProject, type DocsConfig } from "./core/config.ts"
13
+ import { parseDocsConfig, socialNetwork, withHostProject, type DocsConfig } from "./core/config.ts"
14
14
  import {
15
15
  missingPages,
16
16
  normalizeNavigation,
@@ -148,17 +148,27 @@ export function starlightSidebar(
148
148
  )
149
149
  }
150
150
 
151
- const SOCIAL_ICONS = new Set([
152
- "github",
153
- "gitlab",
154
- "linkedin",
155
- "x.com",
156
- "mastodon",
157
- "discord",
158
- "youtube",
159
- "instagram",
160
- "bluesky",
161
- ])
151
+ /**
152
+ * `redirects` de docs.json en redirections d'Astro : une page qui renvoie à la nouvelle
153
+ * adresse (le site est statique). Chemins écrits comme les liens de la doc, `base` ajouté.
154
+ */
155
+ export function astroRedirects(
156
+ redirects: ReadonlyArray<{ source: string; destination: string; permanent?: boolean | undefined }>,
157
+ base: string,
158
+ ): Record<string, { status: 301 | 302; destination: string }> {
159
+ const slash = (path: string) => (path.endsWith("/") ? path : `${path}/`)
160
+ return Object.fromEntries(
161
+ redirects.map((item) => [
162
+ slash(item.source),
163
+ {
164
+ status: item.permanent === false ? 302 : 301,
165
+ destination: item.destination.startsWith("/")
166
+ ? `${base}${slash(item.destination.split("#")[0]!)}${item.destination.includes("#") ? `#${item.destination.split("#")[1]}` : ""}`
167
+ : item.destination,
168
+ },
169
+ ]),
170
+ )
171
+ }
162
172
 
163
173
  function virtualConfigPlugin(data: VirtualConfig) {
164
174
  const id = "virtual:hgdc-docs/config"
@@ -215,9 +225,10 @@ export function createAstroConfig(options: SiteOptions = readSiteOptions()): Ast
215
225
  const theme = themeFor(config.colors?.primary, config.colors?.dark)
216
226
  const logo = typeof config.logo === "string" ? { light: config.logo } : config.logo
217
227
  const asset = (path: string) => join(options.contentDir, path.replace(/^\//, ""))
218
- const social = Object.entries(config.footer?.socials ?? {})
219
- .filter(([icon]) => SOCIAL_ICONS.has(icon))
220
- .map(([icon, href]) => ({ icon: (icon === "x.com" ? "x.com" : icon) as "github", label: icon, href }))
228
+ const social = Object.entries(config.footer?.socials ?? {}).flatMap(([key, href]) => {
229
+ const network = socialNetwork(key)
230
+ return network ? [{ icon: network.icon as "github", label: network.label, href }] : []
231
+ })
221
232
 
222
233
  return {
223
234
  root: file("../template"),
@@ -231,6 +242,7 @@ export function createAstroConfig(options: SiteOptions = readSiteOptions()): Ast
231
242
  site: options.site ?? undefined,
232
243
  ...(base ? { base } : {}),
233
244
  trailingSlash: "always",
245
+ redirects: astroRedirects(config.redirects ?? [], base),
234
246
  markdown: { processor: unified({ remarkPlugins: [remarkAutoImport, [remarkBaseLinks, { base }]] }) },
235
247
  vite: {
236
248
  plugins: [virtualConfigPlugin(data)],
@@ -255,6 +267,8 @@ export function createAstroConfig(options: SiteOptions = readSiteOptions()): Ast
255
267
  Sidebar: file("./overrides/Sidebar.astro"),
256
268
  PageTitle: file("./overrides/PageTitle.astro"),
257
269
  PageFrame: file("./overrides/PageFrame.astro"),
270
+ SocialIcons: file("./overrides/SocialIcons.astro"),
271
+ Footer: file("./overrides/Footer.astro"),
258
272
  },
259
273
  expressiveCode: { themes: ["github-dark-default", "github-light-default"] },
260
274
  pagination: true,
@@ -0,0 +1,154 @@
1
+ ---
2
+ import Default from "@astrojs/starlight/components/Footer.astro"
3
+ import data from "virtual:hgdc-docs/config"
4
+
5
+ // « Cette page t'a aidé ? » au bas de chaque page, envoyé à la console (`/api/v1/feedback`).
6
+ // Sans projet relié, rien n'est affiché : il n'y a personne pour lire les réponses.
7
+ const endpoint = new URL("/api/v1/feedback", data.config.assistant?.endpoint ?? data.apiEndpoint).toString()
8
+ ---
9
+
10
+ {
11
+ data.config.project && (
12
+ <hgdc-feedback class="hgdc-feedback not-content" data-endpoint={endpoint} data-project={data.config.project} data-pagefind-ignore>
13
+ <div data-step="ask">
14
+ <p class="hgdc-feedback__question">Cette page t'a aidé ?</p>
15
+ <div class="hgdc-feedback__choices">
16
+ <button type="button" data-helpful="true">Oui</button>
17
+ <button type="button" data-helpful="false">Non</button>
18
+ </div>
19
+ </div>
20
+ <form data-step="comment" hidden>
21
+ <label for="hgdc-feedback-comment">Qu'est-ce qui manquait ou pourrait être plus clair ? (facultatif)</label>
22
+ <textarea id="hgdc-feedback-comment" name="comment" rows="3" maxlength="1000"></textarea>
23
+ <div class="hgdc-feedback__choices">
24
+ <button type="submit">Envoyer</button>
25
+ <button type="button" data-skip>Passer</button>
26
+ </div>
27
+ </form>
28
+ <p data-step="done" class="hgdc-feedback__done" role="status" hidden>Merci, c'est noté.</p>
29
+ </hgdc-feedback>
30
+ )
31
+ }
32
+ <Default />
33
+
34
+ <script>
35
+ class HgdcFeedback extends HTMLElement {
36
+ #helpful: boolean | null = null
37
+
38
+ connectedCallback() {
39
+ for (const button of this.querySelectorAll<HTMLButtonElement>("[data-helpful]"))
40
+ button.addEventListener("click", () => this.#choose(button.dataset.helpful === "true"))
41
+ const form = this.querySelector("form")!
42
+ form.addEventListener("submit", (event) => {
43
+ event.preventDefault()
44
+ const comment = new FormData(form).get("comment")
45
+ void this.#send(typeof comment === "string" ? comment.trim() : "")
46
+ })
47
+ this.querySelector("[data-skip]")?.addEventListener("click", () => void this.#send(""))
48
+ }
49
+
50
+ #show(step: "ask" | "comment" | "done") {
51
+ for (const element of this.querySelectorAll<HTMLElement>("[data-step]")) element.hidden = element.dataset.step !== step
52
+ if (step === "comment") this.querySelector("textarea")?.focus()
53
+ }
54
+
55
+ #choose(helpful: boolean) {
56
+ this.#helpful = helpful
57
+ // Un oui part tout de suite ; un non attend le commentaire (ou « Passer ») : un seul envoi par avis.
58
+ if (helpful) void this.#send("")
59
+ else this.#show("comment")
60
+ }
61
+
62
+ async #send(comment: string) {
63
+ if (this.#helpful === null) return
64
+ try {
65
+ await fetch(this.dataset.endpoint!, {
66
+ method: "POST",
67
+ headers: { "content-type": "application/json" },
68
+ body: JSON.stringify({
69
+ project: this.dataset.project,
70
+ page: location.pathname,
71
+ helpful: this.#helpful,
72
+ ...(comment ? { comment } : {}),
73
+ }),
74
+ keepalive: true,
75
+ })
76
+ } catch {
77
+ // Un retour perdu ne gêne pas la lecture : rien à afficher.
78
+ }
79
+ this.#show("done")
80
+ }
81
+ }
82
+ customElements.define("hgdc-feedback", HgdcFeedback)
83
+ </script>
84
+
85
+ <style>
86
+ .hgdc-feedback {
87
+ display: block;
88
+ margin-block: 2.5rem 1.5rem;
89
+ padding: 1rem 1.25rem;
90
+ border-radius: 1rem;
91
+ background: var(--sl-color-gray-6);
92
+ }
93
+ .hgdc-feedback__question,
94
+ .hgdc-feedback label {
95
+ margin: 0 0 0.6rem;
96
+ font-size: var(--sl-text-sm);
97
+ font-weight: 500;
98
+ color: var(--sl-color-white);
99
+ }
100
+ .hgdc-feedback [data-step="ask"] {
101
+ display: flex;
102
+ flex-wrap: wrap;
103
+ align-items: center;
104
+ justify-content: space-between;
105
+ gap: 0.75rem;
106
+ }
107
+ .hgdc-feedback [data-step="ask"] .hgdc-feedback__question {
108
+ margin: 0;
109
+ }
110
+ .hgdc-feedback [hidden] {
111
+ display: none !important;
112
+ }
113
+ .hgdc-feedback__choices {
114
+ display: flex;
115
+ gap: 0.5rem;
116
+ }
117
+ .hgdc-feedback button {
118
+ border: 1px solid var(--sl-color-gray-5);
119
+ background: var(--sl-color-black);
120
+ color: var(--sl-color-white);
121
+ border-radius: 999px;
122
+ padding: 0.35rem 1rem;
123
+ font: inherit;
124
+ font-size: var(--sl-text-sm);
125
+ cursor: pointer;
126
+ }
127
+ .hgdc-feedback button:hover {
128
+ border-color: var(--sl-color-accent);
129
+ }
130
+ .hgdc-feedback button[type="submit"] {
131
+ background: var(--sl-color-accent);
132
+ border-color: var(--sl-color-accent);
133
+ color: var(--hgdc-on-accent, #fff);
134
+ }
135
+ .hgdc-feedback form {
136
+ display: grid;
137
+ gap: 0.6rem;
138
+ }
139
+ .hgdc-feedback textarea {
140
+ width: 100%;
141
+ border-radius: 0.75rem;
142
+ border: 1px solid var(--sl-color-gray-5);
143
+ background: var(--sl-color-black);
144
+ color: var(--sl-color-white);
145
+ padding: 0.5rem 0.75rem;
146
+ font: inherit;
147
+ font-size: var(--sl-text-sm);
148
+ }
149
+ .hgdc-feedback__done {
150
+ margin: 0;
151
+ font-size: var(--sl-text-sm);
152
+ color: var(--sl-color-gray-2);
153
+ }
154
+ </style>
@@ -1,14 +1,14 @@
1
- ---
2
- import Default from "@astrojs/starlight/components/Head.astro"
3
- import data from "virtual:hgdc-docs/config"
4
-
5
- import { markdownPath } from "../core/llms.ts"
6
- import { withBase, withoutBase } from "../core/navigation.ts"
7
-
8
- // llmstxt.org : chaque page annonce sa version Markdown et l'index qui la décrit.
9
- const markdown = withBase(data.base, markdownPath(withoutBase(data.base, Astro.url.pathname)))
10
- ---
11
-
12
- <Default><slot /></Default>
13
- <link rel="alternate" type="text/markdown" href={markdown} />
14
- <link rel="describedby" type="text/markdown" href={withBase(data.base, "/llms.txt")} />
1
+ ---
2
+ import Default from "@astrojs/starlight/components/Head.astro"
3
+ import data from "virtual:hgdc-docs/config"
4
+
5
+ import { markdownPath } from "../core/llms.ts"
6
+ import { withBase, withoutBase } from "../core/navigation.ts"
7
+
8
+ // llmstxt.org : chaque page annonce sa version Markdown et l'index qui la décrit.
9
+ const markdown = withBase(data.base, markdownPath(withoutBase(data.base, Astro.url.pathname)))
10
+ ---
11
+
12
+ <Default><slot /></Default>
13
+ <link rel="alternate" type="text/markdown" href={markdown} />
14
+ <link rel="describedby" type="text/markdown" href={withBase(data.base, "/llms.txt")} />
@@ -1,95 +1,95 @@
1
- ---
2
- import Default from "@astrojs/starlight/components/PageTitle.astro"
3
- import data from "virtual:hgdc-docs/config"
4
-
5
- import { markdownPath } from "../core/llms.ts"
6
- import { withBase, withoutBase } from "../core/navigation.ts"
7
-
8
- const markdown = withBase(data.base, markdownPath(withoutBase(data.base, Astro.url.pathname)))
9
- const assistant = Boolean(data.config.project) && data.config.assistant?.enabled !== false
10
- ---
11
-
12
- <div class="hgdc-title">
13
- <Default><slot /></Default>
14
- <div class="hgdc-page-actions not-content" data-hgdc-skip data-pagefind-ignore>
15
- <button type="button" class="hgdc-action" data-hgdc-copy={markdown}>
16
- <svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16"
17
- ><rect x="8" y="8" width="12" height="12" rx="3" fill="none" stroke="currentColor" stroke-width="1.8"
18
- ></rect><path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2" fill="none" stroke="currentColor" stroke-width="1.8"
19
- ></path></svg
20
- >
21
- <span data-label>Copier pour un LLM</span>
22
- </button>
23
- <details class="hgdc-menu">
24
- <summary class="hgdc-action hgdc-action--icon" aria-label="Autres formats de la page">
25
- <svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16"
26
- ><path d="m6 9 6 6 6-6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"></path></svg
27
- >
28
- </summary>
29
- <ul class="hgdc-menu__list">
30
- <li><a href={markdown}>Voir en Markdown</a></li>
31
- <li><a data-hgdc-open="claude" href="https://claude.ai/new" target="_blank" rel="noopener">Ouvrir dans Claude</a></li>
32
- <li><a data-hgdc-open="chatgpt" href="https://chatgpt.com/" target="_blank" rel="noopener">Ouvrir dans ChatGPT</a></li>
33
- <li><a href={withBase(data.base, "/llms.txt")}>Index pour les agents (llms.txt)</a></li>
34
- </ul>
35
- </details>
36
- {
37
- assistant && (
38
- <button type="button" class="hgdc-action" data-hgdc-ask>
39
- <svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16">
40
- <path d="M12 3l1.9 4.6L18.5 9.5l-4.6 1.9L12 16l-1.9-4.6L5.5 9.5l4.6-1.9z" fill="currentColor" />
41
- </svg>
42
- <span>Demander à l'assistant</span>
43
- </button>
44
- )
45
- }
46
- <span class="sr-only" role="status" aria-live="polite" data-hgdc-copy-status></span>
47
- </div>
48
- </div>
49
-
50
- <script>
51
- const status = document.querySelector<HTMLElement>("[data-hgdc-copy-status]")
52
-
53
- for (const button of document.querySelectorAll<HTMLButtonElement>("[data-hgdc-copy]")) {
54
- const label = button.querySelector<HTMLElement>("[data-label]")!
55
- button.addEventListener("click", async () => {
56
- const done = (text: string) => {
57
- label.textContent = text
58
- if (status) status.textContent = text
59
- setTimeout(() => (label.textContent = "Copier pour un LLM"), 2000)
60
- }
61
- try {
62
- const response = await fetch(button.dataset.hgdcCopy!)
63
- if (!response.ok) throw new Error(String(response.status))
64
- await navigator.clipboard.writeText(await response.text())
65
- done("Page copiée")
66
- } catch {
67
- done("Copie impossible")
68
- }
69
- })
70
- }
71
-
72
- // « Ouvrir dans… » : l'assistant externe lit la version Markdown de cette page.
73
- const markdownUrl = new URL(document.querySelector<HTMLElement>("[data-hgdc-copy]")!.dataset.hgdcCopy!, location.href)
74
- const prompt = `Lis ${markdownUrl.href} et réponds à mes questions sur cette page.`
75
- for (const link of document.querySelectorAll<HTMLAnchorElement>("[data-hgdc-open]")) {
76
- const base = link.dataset.hgdcOpen === "claude" ? "https://claude.ai/new" : "https://chatgpt.com/"
77
- link.href = `${base}?q=${encodeURIComponent(prompt)}`
78
- }
79
-
80
- // Le menu se ferme au clic extérieur et à Échap, comme un menu natif.
81
- for (const menu of document.querySelectorAll<HTMLDetailsElement>(".hgdc-menu")) {
82
- document.addEventListener("click", (event) => {
83
- if (menu.open && !menu.contains(event.target as Node)) menu.open = false
84
- })
85
- menu.addEventListener("keydown", (event) => {
86
- if (event.key === "Escape" && menu.open) {
87
- menu.open = false
88
- menu.querySelector("summary")?.focus()
89
- }
90
- })
91
- }
92
-
93
- for (const button of document.querySelectorAll("[data-hgdc-ask]"))
94
- button.addEventListener("click", () => document.dispatchEvent(new CustomEvent("hgdc:assistant-open")))
95
- </script>
1
+ ---
2
+ import Default from "@astrojs/starlight/components/PageTitle.astro"
3
+ import data from "virtual:hgdc-docs/config"
4
+
5
+ import { markdownPath } from "../core/llms.ts"
6
+ import { withBase, withoutBase } from "../core/navigation.ts"
7
+
8
+ const markdown = withBase(data.base, markdownPath(withoutBase(data.base, Astro.url.pathname)))
9
+ const assistant = Boolean(data.config.project) && data.config.assistant?.enabled !== false
10
+ ---
11
+
12
+ <div class="hgdc-title">
13
+ <Default><slot /></Default>
14
+ <div class="hgdc-page-actions not-content" data-hgdc-skip data-pagefind-ignore>
15
+ <button type="button" class="hgdc-action" data-hgdc-copy={markdown}>
16
+ <svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16"
17
+ ><rect x="8" y="8" width="12" height="12" rx="3" fill="none" stroke="currentColor" stroke-width="1.8"
18
+ ></rect><path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2" fill="none" stroke="currentColor" stroke-width="1.8"
19
+ ></path></svg
20
+ >
21
+ <span data-label>Copier pour un LLM</span>
22
+ </button>
23
+ <details class="hgdc-menu">
24
+ <summary class="hgdc-action hgdc-action--icon" aria-label="Autres formats de la page">
25
+ <svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16"
26
+ ><path d="m6 9 6 6 6-6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"></path></svg
27
+ >
28
+ </summary>
29
+ <ul class="hgdc-menu__list">
30
+ <li><a href={markdown}>Voir en Markdown</a></li>
31
+ <li><a data-hgdc-open="claude" href="https://claude.ai/new" target="_blank" rel="noopener">Ouvrir dans Claude</a></li>
32
+ <li><a data-hgdc-open="chatgpt" href="https://chatgpt.com/" target="_blank" rel="noopener">Ouvrir dans ChatGPT</a></li>
33
+ <li><a href={withBase(data.base, "/llms.txt")}>Index pour les agents (llms.txt)</a></li>
34
+ </ul>
35
+ </details>
36
+ {
37
+ assistant && (
38
+ <button type="button" class="hgdc-action" data-hgdc-ask>
39
+ <svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16">
40
+ <path d="M12 3l1.9 4.6L18.5 9.5l-4.6 1.9L12 16l-1.9-4.6L5.5 9.5l4.6-1.9z" fill="currentColor" />
41
+ </svg>
42
+ <span>Demander à l'assistant</span>
43
+ </button>
44
+ )
45
+ }
46
+ <span class="sr-only" role="status" aria-live="polite" data-hgdc-copy-status></span>
47
+ </div>
48
+ </div>
49
+
50
+ <script>
51
+ const status = document.querySelector<HTMLElement>("[data-hgdc-copy-status]")
52
+
53
+ for (const button of document.querySelectorAll<HTMLButtonElement>("[data-hgdc-copy]")) {
54
+ const label = button.querySelector<HTMLElement>("[data-label]")!
55
+ button.addEventListener("click", async () => {
56
+ const done = (text: string) => {
57
+ label.textContent = text
58
+ if (status) status.textContent = text
59
+ setTimeout(() => (label.textContent = "Copier pour un LLM"), 2000)
60
+ }
61
+ try {
62
+ const response = await fetch(button.dataset.hgdcCopy!)
63
+ if (!response.ok) throw new Error(String(response.status))
64
+ await navigator.clipboard.writeText(await response.text())
65
+ done("Page copiée")
66
+ } catch {
67
+ done("Copie impossible")
68
+ }
69
+ })
70
+ }
71
+
72
+ // « Ouvrir dans… » : l'assistant externe lit la version Markdown de cette page.
73
+ const markdownUrl = new URL(document.querySelector<HTMLElement>("[data-hgdc-copy]")!.dataset.hgdcCopy!, location.href)
74
+ const prompt = `Lis ${markdownUrl.href} et réponds à mes questions sur cette page.`
75
+ for (const link of document.querySelectorAll<HTMLAnchorElement>("[data-hgdc-open]")) {
76
+ const base = link.dataset.hgdcOpen === "claude" ? "https://claude.ai/new" : "https://chatgpt.com/"
77
+ link.href = `${base}?q=${encodeURIComponent(prompt)}`
78
+ }
79
+
80
+ // Le menu se ferme au clic extérieur et à Échap, comme un menu natif.
81
+ for (const menu of document.querySelectorAll<HTMLDetailsElement>(".hgdc-menu")) {
82
+ document.addEventListener("click", (event) => {
83
+ if (menu.open && !menu.contains(event.target as Node)) menu.open = false
84
+ })
85
+ menu.addEventListener("keydown", (event) => {
86
+ if (event.key === "Escape" && menu.open) {
87
+ menu.open = false
88
+ menu.querySelector("summary")?.focus()
89
+ }
90
+ })
91
+ }
92
+
93
+ for (const button of document.querySelectorAll("[data-hgdc-ask]"))
94
+ button.addEventListener("click", () => document.dispatchEvent(new CustomEvent("hgdc:assistant-open")))
95
+ </script>
@@ -1,60 +1,60 @@
1
- ---
2
- import Default from "@astrojs/starlight/components/Sidebar.astro"
3
- import data from "virtual:hgdc-docs/config"
4
-
5
- import { pagesOf, pageToPath, withBase } from "../core/navigation.ts"
6
-
7
- // Onglets et versions de docs.json, en tête de la sidebar : visibles sur ordinateur et dans
8
- // le menu mobile, sans toucher à la hauteur de l'en-tête de Starlight.
9
- const sections = data.tree.versions.flatMap((version) => version.tabs.map((tab) => ({ version, tab })))
10
- const index = Astro.locals.hgdcSection ?? 0
11
- const current = sections[index] ?? sections[0]!
12
- const firstPath = (items: Parameters<typeof pagesOf>[0]) => {
13
- const first = pagesOf(items)[0]
14
- return withBase(data.base, first ? pageToPath(first) : "/")
15
- }
16
- const tabs = current.version.tabs.filter((tab) => tab.label)
17
- const versions = data.tree.versions.filter((version) => version.label)
18
- const tabPosition = current.version.tabs.indexOf(current.tab)
19
- const versionHref = (version: (typeof versions)[number]) =>
20
- firstPath((version.tabs[tabPosition] ?? version.tabs[0]!).items)
21
- ---
22
-
23
- {
24
- (tabs.length > 1 || versions.length > 1) && (
25
- <div class="hgdc-sections">
26
- {versions.length > 1 && (
27
- <label class="hgdc-version">
28
- <span class="sr-only">Version de la documentation</span>
29
- <select data-hgdc-version>
30
- {versions.map((version) => (
31
- <option value={versionHref(version)} selected={version === current.version}>
32
- {version.label}
33
- {version.isDefault ? " (actuelle)" : ""}
34
- </option>
35
- ))}
36
- </select>
37
- </label>
38
- )}
39
- {tabs.length > 1 && (
40
- <nav aria-label="Sections de la documentation">
41
- <ul class="hgdc-tabs">
42
- {tabs.map((tab) => (
43
- <li>
44
- <a href={firstPath(tab.items)} aria-current={tab === current.tab ? "page" : undefined}>
45
- {tab.label}
46
- </a>
47
- </li>
48
- ))}
49
- </ul>
50
- </nav>
51
- )}
52
- </div>
53
- )
54
- }
55
- <Default><slot /></Default>
56
-
57
- <script>
58
- for (const select of document.querySelectorAll<HTMLSelectElement>("[data-hgdc-version]"))
59
- select.addEventListener("change", () => window.location.assign(select.value))
60
- </script>
1
+ ---
2
+ import Default from "@astrojs/starlight/components/Sidebar.astro"
3
+ import data from "virtual:hgdc-docs/config"
4
+
5
+ import { pagesOf, pageToPath, withBase } from "../core/navigation.ts"
6
+
7
+ // Onglets et versions de docs.json, en tête de la sidebar : visibles sur ordinateur et dans
8
+ // le menu mobile, sans toucher à la hauteur de l'en-tête de Starlight.
9
+ const sections = data.tree.versions.flatMap((version) => version.tabs.map((tab) => ({ version, tab })))
10
+ const index = Astro.locals.hgdcSection ?? 0
11
+ const current = sections[index] ?? sections[0]!
12
+ const firstPath = (items: Parameters<typeof pagesOf>[0]) => {
13
+ const first = pagesOf(items)[0]
14
+ return withBase(data.base, first ? pageToPath(first) : "/")
15
+ }
16
+ const tabs = current.version.tabs.filter((tab) => tab.label)
17
+ const versions = data.tree.versions.filter((version) => version.label)
18
+ const tabPosition = current.version.tabs.indexOf(current.tab)
19
+ const versionHref = (version: (typeof versions)[number]) =>
20
+ firstPath((version.tabs[tabPosition] ?? version.tabs[0]!).items)
21
+ ---
22
+
23
+ {
24
+ (tabs.length > 1 || versions.length > 1) && (
25
+ <div class="hgdc-sections">
26
+ {versions.length > 1 && (
27
+ <label class="hgdc-version">
28
+ <span class="sr-only">Version de la documentation</span>
29
+ <select data-hgdc-version>
30
+ {versions.map((version) => (
31
+ <option value={versionHref(version)} selected={version === current.version}>
32
+ {version.label}
33
+ {version.isDefault ? " (actuelle)" : ""}
34
+ </option>
35
+ ))}
36
+ </select>
37
+ </label>
38
+ )}
39
+ {tabs.length > 1 && (
40
+ <nav aria-label="Sections de la documentation">
41
+ <ul class="hgdc-tabs">
42
+ {tabs.map((tab) => (
43
+ <li>
44
+ <a href={firstPath(tab.items)} aria-current={tab === current.tab ? "page" : undefined}>
45
+ {tab.label}
46
+ </a>
47
+ </li>
48
+ ))}
49
+ </ul>
50
+ </nav>
51
+ )}
52
+ </div>
53
+ )
54
+ }
55
+ <Default><slot /></Default>
56
+
57
+ <script>
58
+ for (const select of document.querySelectorAll<HTMLSelectElement>("[data-hgdc-version]"))
59
+ select.addEventListener("change", () => window.location.assign(select.value))
60
+ </script>
@@ -0,0 +1,59 @@
1
+ ---
2
+ import Default from "@astrojs/starlight/components/SocialIcons.astro"
3
+ import data from "virtual:hgdc-docs/config"
4
+
5
+ /** Barre du haut : liens de `navbar.links`, bouton `navbar.primary`, puis les réseaux. */
6
+ const links = data.config.navbar?.links ?? []
7
+ const primary = data.config.navbar?.primary
8
+ ---
9
+
10
+ {
11
+ (links.length > 0 || primary) && (
12
+ <nav class="hgdc-navbar" aria-label="Liens">
13
+ {links.map((link) => (
14
+ <a class="hgdc-navbar__link" href={link.href}>
15
+ {link.label}
16
+ </a>
17
+ ))}
18
+ {primary && (
19
+ <a class="hgdc-navbar__primary" href={primary.href}>
20
+ {primary.label}
21
+ </a>
22
+ )}
23
+ </nav>
24
+ )
25
+ }
26
+ <Default />
27
+
28
+ <style>
29
+ .hgdc-navbar {
30
+ display: none;
31
+ align-items: center;
32
+ gap: 1rem;
33
+ margin-inline-end: 0.5rem;
34
+ }
35
+ @media (min-width: 72rem) {
36
+ .hgdc-navbar {
37
+ display: flex;
38
+ }
39
+ }
40
+ .hgdc-navbar__link {
41
+ color: var(--sl-color-gray-2);
42
+ font-size: var(--sl-text-sm);
43
+ text-decoration: none;
44
+ white-space: nowrap;
45
+ }
46
+ .hgdc-navbar__link:hover {
47
+ color: var(--sl-color-white);
48
+ }
49
+ .hgdc-navbar__primary {
50
+ background: var(--sl-color-accent);
51
+ color: var(--hgdc-on-accent, #fff);
52
+ border-radius: 999px;
53
+ padding: 0.35rem 0.9rem;
54
+ font-size: var(--sl-text-sm);
55
+ font-weight: 500;
56
+ text-decoration: none;
57
+ white-space: nowrap;
58
+ }
59
+ </style>
@@ -1 +1,4 @@
1
- export default new Map();
1
+
2
+
3
+ export default new Map([]);
4
+