diff options
Diffstat (limited to 'website')
| -rw-r--r-- | website/.gitignore | 1 | ||||
| -rw-r--r-- | website/astro.config.mjs | 2 | ||||
| -rw-r--r-- | website/scripts/prepare-docs.mjs | 80 | ||||
| -rw-r--r-- | website/src/components/Header.astro | 149 | ||||
| -rw-r--r-- | website/src/content.config.ts | 3 |
5 files changed, 230 insertions, 5 deletions
diff --git a/website/.gitignore b/website/.gitignore index 4466e0a1..2d6ddb63 100644 --- a/website/.gitignore +++ b/website/.gitignore @@ -2,3 +2,4 @@ node_modules/ public/ dist/ .astro/ +src/content/docs/preview/ diff --git a/website/astro.config.mjs b/website/astro.config.mjs index badd2873..2da2dd44 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -62,6 +62,7 @@ export default defineConfig({ }, ], components: { + Header: './src/components/Header.astro', SiteTitle: './src/components/SiteTitle.astro', }, customCss: ['./src/styles/starlight.css'], @@ -129,6 +130,7 @@ export default defineConfig({ label: 'Updates', items: [ { label: 'Releases', link: '/releases/' }, + { label: 'Preview docs', slug: 'docs/preview' }, { label: 'Blog', link: '/blog/' }, ], }, diff --git a/website/scripts/prepare-docs.mjs b/website/scripts/prepare-docs.mjs index c6fefaf7..19201a18 100644 --- a/website/scripts/prepare-docs.mjs +++ b/website/scripts/prepare-docs.mjs @@ -1,12 +1,23 @@ -import { cp, mkdir, rm } from 'node:fs/promises'; -import { dirname, resolve } from 'node:path'; +import { cp, mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises'; +import { dirname, join, relative, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import process from 'node:process'; const websiteDir = dirname(fileURLToPath(import.meta.url)); const repoRoot = resolve(websiteDir, '../..'); const publicDir = resolve(repoRoot, 'website/public'); +const stableDocsDir = resolve(repoRoot, 'website/src/content/docs'); +const previewDocsSourceDir = resolve(repoRoot, 'docs/next/website/src/content/docs'); +const previewDocsDir = resolve(stableDocsDir, 'preview'); -await preparePublicAssets(); +if (process.argv[2] === '--rewrite-preview-doc-fixture') { + const chunks = []; + for await (const chunk of process.stdin) chunks.push(chunk); + process.stdout.write(rewritePreviewDocContent(Buffer.concat(chunks).toString('utf8'))); +} else { + await preparePublicAssets(); + await preparePreviewDocs(); +} async function preparePublicAssets() { await rm(publicDir, { recursive: true, force: true }); @@ -15,11 +26,17 @@ async function preparePublicAssets() { for (const file of [ 'install.sh', 'latest.json', + 'preview.json', 'robots.txt', '_headers', '_redirects', ]) { - await cp(resolve(repoRoot, 'website', file), resolve(publicDir, file)); + const source = resolve(repoRoot, 'website', file); + try { + await cp(source, resolve(publicDir, file)); + } catch (error) { + if (file !== 'preview.json' || error.code !== 'ENOENT') throw error; + } } for (const directory of ['assets', 'css']) { @@ -28,3 +45,58 @@ async function preparePublicAssets() { }); } } + +async function preparePreviewDocs() { + await rm(previewDocsDir, { recursive: true, force: true }); + await copyPreviewDocs(previewDocsSourceDir, previewDocsDir); +} + +async function copyPreviewDocs(sourceDir, destinationDir) { + await mkdir(destinationDir, { recursive: true }); + for (const entry of await readdir(sourceDir, { withFileTypes: true })) { + const source = join(sourceDir, entry.name); + const destination = join(destinationDir, entry.name); + if (entry.isDirectory()) { + await copyPreviewDocs(source, destination); + continue; + } + if (!entry.isFile()) continue; + + const content = await readFile(source, 'utf8'); + const relativePath = relative(previewDocsSourceDir, source); + await writeFile(destination, rewritePreviewDocContent(content, relativePath), 'utf8'); + } +} + +export function rewritePreviewDocContent(content, relativePath = '') { + const rewritten = content + .replaceAll('/docs/', '/docs/preview/') + .replaceAll('../../../public/', '../../../../public/'); + return insertPreviewNotice(rewritten, relativePath); +} + +function insertPreviewNotice(content, relativePath) { + const notice = [ + '> Preview docs describe unreleased preview builds. Stable docs remain at [/docs/](/docs/).', + '', + '', + ].join('\n'); + const indexPrefix = + relativePath === 'index.mdx' + ? content.replace('title: Herdr documentation', 'title: Herdr preview documentation') + : content; + const frontmatter = indexPrefix.match(/^---\n[\s\S]*?\n---\n/); + if (!frontmatter) { + return insertNoticeAfterImports(indexPrefix, notice); + } + const body = indexPrefix.slice(frontmatter[0].length); + return `${frontmatter[0]}\n${insertNoticeAfterImports(body, notice)}`; +} + +function insertNoticeAfterImports(content, notice) { + const imports = content.match(/^(\s*import .+?;\n)+\s*/); + if (!imports) { + return `${notice}${content}`; + } + return `${imports[0]}${notice}${content.slice(imports[0].length)}`; +} diff --git a/website/src/components/Header.astro b/website/src/components/Header.astro new file mode 100644 index 00000000..982efd2e --- /dev/null +++ b/website/src/components/Header.astro @@ -0,0 +1,149 @@ +--- +import config from 'virtual:starlight/user-config'; + +import LanguageSelect from 'virtual:starlight/components/LanguageSelect'; +import Search from 'virtual:starlight/components/Search'; +import SiteTitle from 'virtual:starlight/components/SiteTitle'; +import SocialIcons from 'virtual:starlight/components/SocialIcons'; +import ThemeSelect from 'virtual:starlight/components/ThemeSelect'; + +const shouldRenderSearch = + config.pagefind || config.components.Search !== '@astrojs/starlight/components/Search.astro'; + +const pathname = Astro.url.pathname; +const isDocs = pathname === '/docs/' || pathname.startsWith('/docs/'); +const isPreview = pathname === '/docs/preview/' || pathname.startsWith('/docs/preview/'); +const stableHref = isPreview ? pathname.replace(/^\/docs\/preview\/?/, '/docs/') : pathname; +const previewHref = isPreview + ? pathname + : pathname === '/docs/' + ? '/docs/preview/' + : pathname.replace(/^\/docs\/?/, '/docs/preview/'); +--- + +<div class="header"> + <div class="title-wrapper sl-flex"> + <SiteTitle /> + </div> + <div class="sl-flex print:hidden search-wrapper"> + {isDocs && ( + <nav class="docs-channel-switcher" aria-label="Documentation channel"> + <a href={stableHref} aria-current={!isPreview ? 'page' : undefined}>Stable</a> + <a href={previewHref} aria-current={isPreview ? 'page' : undefined}>Preview</a> + </nav> + )} + {shouldRenderSearch && <Search />} + </div> + <div class="sl-hidden md:sl-flex print:hidden right-group"> + <div class="sl-flex social-icons"> + <SocialIcons /> + </div> + <ThemeSelect /> + <LanguageSelect /> + </div> +</div> + +<style> + @layer starlight.core { + .header { + display: flex; + gap: var(--sl-nav-gap); + justify-content: space-between; + align-items: center; + height: 100%; + } + + .title-wrapper { + overflow: clip; + padding: 0.25rem; + margin: -0.25rem; + min-width: 0; + } + + .search-wrapper { + align-items: center; + gap: 0.75rem; + min-width: 0; + } + + .docs-channel-switcher { + display: flex; + align-items: center; + border: 1px solid var(--sl-color-hairline); + border-radius: 4px; + overflow: hidden; + background: var(--sl-color-bg-nav); + font-size: var(--sl-text-xs); + font-weight: 700; + line-height: 1; + white-space: nowrap; + } + + .docs-channel-switcher a { + color: var(--sl-color-gray-2); + text-decoration: none; + padding: 0.45rem 0.6rem; + } + + .docs-channel-switcher a + a { + border-inline-start: 1px solid var(--sl-color-hairline); + } + + .docs-channel-switcher a[aria-current="page"] { + color: var(--sl-color-accent-high); + background: color-mix(in srgb, var(--sl-color-accent) 14%, transparent); + } + + .docs-channel-switcher a:hover { + color: var(--sl-color-accent-high); + } + + .right-group, + .social-icons { + gap: 1rem; + align-items: center; + } + + .social-icons::after { + content: ""; + height: 2rem; + border-inline-end: 1px solid var(--sl-color-gray-5); + } + + @media (max-width: 38rem) { + .docs-channel-switcher { + display: none; + } + } + + @media (min-width: 50rem) { + :global(:root[data-has-sidebar]) { + --__sidebar-pad: calc(2 * var(--sl-nav-pad-x)); + } + + :global(:root:not([data-has-toc])) { + --__toc-width: 0rem; + } + + .header { + --__sidebar-width: max(0rem, var(--sl-content-inline-start, 0rem) - var(--sl-nav-pad-x)); + --__main-column-fr: calc( + ( + 100% + var(--__sidebar-pad, 0rem) - var(--__toc-width, var(--sl-sidebar-width)) - + (2 * var(--__toc-width, var(--sl-nav-pad-x))) - var(--sl-content-inline-start, 0rem) - + var(--sl-content-width) + ) / 2 + ); + display: grid; + grid-template-columns: + minmax( + calc(var(--__sidebar-width) + max(0rem, var(--__main-column-fr) - var(--sl-nav-gap))), + auto + ) + 1fr + auto; + align-content: center; + } + } + } +</style> diff --git a/website/src/content.config.ts b/website/src/content.config.ts index c9a9b8fd..834d37cf 100644 --- a/website/src/content.config.ts +++ b/website/src/content.config.ts @@ -5,7 +5,8 @@ import { docsSchema } from '@astrojs/starlight/schema'; function docsPath({ entry }: { entry: string }) { const slug = entry.replace(/\.(md|mdx|markdown|mdown|mkdn|mkd|mdwn)$/i, ''); - return slug === 'index' ? 'docs' : `docs/${slug}`; + const normalized = slug.replace(/\/index$/, ''); + return normalized === 'index' ? 'docs' : `docs/${normalized}`; } export const collections = { |
