From c234f221f9be698bc0501c60459e1ae8e67df6c3 Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Wed, 22 Jul 2026 14:58:26 +0300 Subject: feat(docs): publish versioned release documentation --- website/src/components/Banner.astro | 29 ++++++++- website/src/components/ConfigReference.astro | 13 +++- website/src/components/DocsVersionSelect.astro | 84 ++++++++++++++++++++++++++ website/src/components/Head.astro | 7 ++- website/src/components/Header.astro | 58 ++---------------- website/src/components/Search.astro | 28 ++++----- website/src/components/Sidebar.astro | 67 +++++++------------- website/src/components/SiteTitle.astro | 7 ++- 8 files changed, 172 insertions(+), 121 deletions(-) create mode 100644 website/src/components/DocsVersionSelect.astro (limited to 'website/src/components') diff --git a/website/src/components/Banner.astro b/website/src/components/Banner.astro index 8a4bad22..01098659 100644 --- a/website/src/components/Banner.astro +++ b/website/src/components/Banner.astro @@ -1,4 +1,7 @@ --- +import versions from '../data/docs-versions.json'; +import { docsRoute, docsTargetHref } from '../docs-path'; + const { entry, locale, isFallback } = Astro.locals.starlightRoute; const { banner } = entry.data; @@ -8,9 +11,24 @@ const translationNotices = { 'zh-cn': `本页面的翻译由 LLM 生成。如果你发现翻译有误,请在 GitHub 上提交 issue 告诉我们。`, }; const translationNotice = locale && !isFallback ? translationNotices[locale] : undefined; +const docs = docsRoute(Astro.url.pathname); +const archivedVersion = + docs.target !== 'stable' && docs.target !== 'preview' && docs.target !== versions.current + ? docs.target + : undefined; +const latestHref = docsTargetHref(docs, 'stable', versions.scopes); +const archivedNotices = { + root: `This page documents Herdr ${archivedVersion}. Switch to the latest release.`, + ja: `このページは Herdr ${archivedVersion} のドキュメントです。最新版に切り替えてください。`, + 'zh-cn': `本页面是 Herdr ${archivedVersion} 的文档。切换到最新版本。`, +}; +const archivedNotice = archivedVersion ? archivedNotices[locale ?? 'root'] : undefined; --- {banner &&
} +{archivedNotice && ( +
+)} {translationNotice && (
)} @@ -31,7 +49,8 @@ const translationNotice = locale && !isFallback ? translationNotices[locale] : u color: var(--__sl-banner-text); } - .translation-notice { + .translation-notice, + .version-notice { padding: 0.5rem var(--sl-nav-pad-x); background-color: var(--sl-color-bg-nav); border-bottom: 1px solid var(--sl-color-hairline); @@ -41,8 +60,16 @@ const translationNotice = locale && !isFallback ? translationNotices[locale] : u text-align: center; text-wrap: balance; } + .version-notice { + background-color: var(--sl-color-orange-low); + border-bottom-color: var(--sl-color-orange); + color: var(--sl-color-orange-high); + } .translation-notice :global(a) { color: var(--sl-color-text-accent); } + .version-notice :global(a) { + color: var(--sl-color-orange-high); + } } diff --git a/website/src/components/ConfigReference.astro b/website/src/components/ConfigReference.astro index d951f1cc..33894993 100644 --- a/website/src/components/ConfigReference.astro +++ b/website/src/components/ConfigReference.astro @@ -1,6 +1,8 @@ --- import stableReference from '../data/config-reference.json'; import previewReference from '../data/config-reference-preview.json'; +import versionReferences from '../data/config-reference-versions.json'; +import { docsVersion } from '../docs-path'; interface Props { placeholder?: string; @@ -14,9 +16,16 @@ const { keyCountLabel = 'keys', } = Astro.props; -const reference = Astro.url.pathname.includes('/docs/preview/') +const version = docsVersion(Astro.url.pathname); +const archivedReferences = versionReferences as Record; +const reference = version === 'preview' ? previewReference - : stableReference; + : version === 'stable' + ? stableReference + : archivedReferences[version]; +if (!reference) { + throw new Error(`documentation version ${version} has no archived configuration reference`); +} const totalKeys = reference.sections.reduce((sum, section) => sum + section.keys.length, 0); --- diff --git a/website/src/components/DocsVersionSelect.astro b/website/src/components/DocsVersionSelect.astro new file mode 100644 index 00000000..cc6ea67f --- /dev/null +++ b/website/src/components/DocsVersionSelect.astro @@ -0,0 +1,84 @@ +--- +import versions from '../data/docs-versions.json'; +import { docsRoute, docsTargetHref } from '../docs-path'; + +const route = docsRoute(Astro.url.pathname); +const locale = route.locale ?? 'root'; +const labels = { + root: { accessible: 'Documentation version', latest: 'Latest', next: 'Unreleased' }, + ja: { accessible: 'ドキュメントのバージョン', latest: '最新', next: '未リリース' }, + 'zh-cn': { accessible: '文档版本', latest: '最新', next: '未发布' }, +}[locale]; +const scopes = versions.scopes; +--- + +{route.isDocs && ( + +)} + + + + diff --git a/website/src/components/Head.astro b/website/src/components/Head.astro index b1214cb1..9987173b 100644 --- a/website/src/components/Head.astro +++ b/website/src/components/Head.astro @@ -1,9 +1,10 @@ --- -import { docsChannel } from '../docs-path'; +import { docsVersion } from '../docs-path'; const { head } = Astro.locals.starlightRoute; -const channel = docsChannel(Astro.url.pathname); +const version = docsVersion(Astro.url.pathname); --- {head.map(({ tag: Tag, attrs, content }) => )} - + +{version !== 'stable' && } diff --git a/website/src/components/Header.astro b/website/src/components/Header.astro index 982efd2e..cfa3d759 100644 --- a/website/src/components/Header.astro +++ b/website/src/components/Header.astro @@ -6,19 +6,12 @@ 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'; +import DocsVersionSelect from './DocsVersionSelect.astro'; +import { docsRoute } from '../docs-path'; 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/'); +const isDocs = docsRoute(Astro.url.pathname).isDocs; ---
@@ -26,12 +19,7 @@ const previewHref = isPreview
- {isDocs && ( - - )} + {isDocs && } {shouldRenderSearch && }
@@ -66,38 +54,6 @@ const previewHref = isPreview 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; @@ -110,12 +66,6 @@ const previewHref = isPreview 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)); diff --git a/website/src/components/Search.astro b/website/src/components/Search.astro index 2a31ea24..cceeed34 100644 --- a/website/src/components/Search.astro +++ b/website/src/components/Search.astro @@ -1,25 +1,25 @@ --- import StarlightSearch from '@astrojs/starlight/components/Search.astro'; -import { docsChannel } from '../docs-path'; +import { docsVersion } from '../docs-path'; -const channel = docsChannel(Astro.url.pathname); +const version = docsVersion(Astro.url.pathname); --- - + - + diff --git a/website/src/components/Sidebar.astro b/website/src/components/Sidebar.astro index 8ded83bb..08d31f79 100644 --- a/website/src/components/Sidebar.astro +++ b/website/src/components/Sidebar.astro @@ -2,62 +2,39 @@ import MobileMenuFooter from 'virtual:starlight/components/MobileMenuFooter'; import SidebarPersister from '@astrojs/starlight/components/SidebarPersister.astro'; import SidebarSublist from '@astrojs/starlight/components/SidebarSublist.astro'; +import versions from '../data/docs-versions.json'; +import { docsRoute, docsTargetHref } from '../docs-path'; const { sidebar } = Astro.locals.starlightRoute; const pathname = Astro.url.pathname; -const isPreview = pathname === '/docs/preview/' || pathname.startsWith('/docs/preview/'); -const channelSidebar = isPreview ? addPreviewOnlyItems(rewriteSidebar(sidebar)) : sidebar; +const route = docsRoute(pathname); +const scopedSidebar = route.target === 'stable' ? sidebar : rewriteSidebar(sidebar); -function addPreviewOnlyItems(entries) { - return entries.map((entry) => { - if (entry.type !== 'group' || entry.label !== 'Start here') return entry; - - const windowsBeta = { - type: 'link', - label: 'Windows beta', - href: '/docs/preview/windows-beta/', - badge: undefined, - isCurrent: samePath('/docs/preview/windows-beta/', pathname), - attrs: {}, - }; - const nextEntries = []; - for (const child of entry.entries) { - nextEntries.push(child); - if (child.type === 'link' && child.label === 'Install') { - nextEntries.push(windowsBeta); +function rewriteSidebar(entries) { + return entries.flatMap((entry) => { + if (entry.type === 'link') { + const linkRoute = docsRoute(entry.href); + if (!linkRoute.isDocs) return [entry]; + if (linkRoute.target !== 'stable') { + return linkRoute.target === route.target + ? [{ ...entry, isCurrent: samePath(entry.href, pathname) }] + : []; } - } - return { - ...entry, - entries: nextEntries, - }; - }); -} + const localeKey = route.locale ?? 'root'; + const availablePages = versions.scopes[route.target]?.locales[localeKey] ?? []; + if (!availablePages.includes(linkRoute.page)) return []; -function rewriteSidebar(entries) { - return entries.map((entry) => { - if (entry.type === 'link') { - const href = previewHref(entry.href); - return { - ...entry, - href, - isCurrent: samePath(href, pathname), - }; + const href = docsTargetHref({ ...linkRoute, locale: route.locale }, route.target, versions.scopes); + return [{ ...entry, href, isCurrent: samePath(href, pathname) }]; } + const rewrittenEntries = rewriteSidebar(entry.entries); - return { - ...entry, - entries: rewrittenEntries, - }; + if (rewrittenEntries.length === 0) return []; + return [{ ...entry, entries: rewrittenEntries }]; }); } -function previewHref(href) { - if (!href.startsWith('/docs/') || href.startsWith('/docs/preview/')) return href; - return href === '/docs/' ? '/docs/preview/' : href.replace(/^\/docs\//, '/docs/preview/'); -} - function samePath(left, right) { return stripTrailingSlash(left) === stripTrailingSlash(right); } @@ -68,7 +45,7 @@ function stripTrailingSlash(path) { --- - +
diff --git a/website/src/components/SiteTitle.astro b/website/src/components/SiteTitle.astro index 576e7635..3e5af0a3 100644 --- a/website/src/components/SiteTitle.astro +++ b/website/src/components/SiteTitle.astro @@ -1,8 +1,11 @@ --- +import { docsRoute } from '../docs-path'; + const { siteTitle, siteTitleHref, locale } = Astro.locals.starlightRoute; -const isPreview = Astro.url.pathname === '/docs/preview/' || Astro.url.pathname.startsWith('/docs/preview/'); +const route = docsRoute(Astro.url.pathname); const localePrefix = locale ? `/${locale}` : ''; -const docsHref = isPreview ? '/docs/preview/' : `${localePrefix}/docs/`; +const targetSegment = route.target === 'stable' ? '' : `/${route.target}`; +const docsHref = `${localePrefix}/docs${targetSegment}/`; ---
-- cgit v1.2.3