From 5c6996cd248e9da735a14e7fcaf638b51f2796bc Mon Sep 17 00:00:00 2001 From: HiDeoo Date: Sat, 14 Dec 2024 13:28:18 +0100 Subject: Fix autogenerated sidebar issue (#2688) Co-authored-by: delucis <357379+delucis@users.noreply.github.com>--- .changeset/wild-laws-play.md | 5 ++++ .../starlight/__tests__/basics/navigation.test.ts | 29 ++++++++++++++++++-- packages/starlight/__tests__/test-utils.ts | 6 +++- packages/starlight/utils/navigation.ts | 32 ++++++++++++++++------ packages/starlight/utils/slugs.ts | 3 +- 5 files changed, 62 insertions(+), 13 deletions(-) create mode 100644 .changeset/wild-laws-play.md diff --git a/.changeset/wild-laws-play.md b/.changeset/wild-laws-play.md new file mode 100644 index 00000000..d1d1ace2 --- /dev/null +++ b/.changeset/wild-laws-play.md @@ -0,0 +1,5 @@ +--- +'@astrojs/starlight': patch +--- + +Fixes an issue with autogenerated sidebars when using Starlight with Astro's new Content Layer API where group names would be sluggified. diff --git a/packages/starlight/__tests__/basics/navigation.test.ts b/packages/starlight/__tests__/basics/navigation.test.ts index 90676507..0b4e5bd7 100644 --- a/packages/starlight/__tests__/basics/navigation.test.ts +++ b/packages/starlight/__tests__/basics/navigation.test.ts @@ -9,6 +9,7 @@ vi.mock('astro:content', async () => ['guides/authoring-content.mdx', { title: 'Authoring Markdown' }], ['reference/frontmatter.md', { title: 'Frontmatter Reference', sidebar: { hidden: true } }], ['guides/project-structure.mdx', { title: 'Project Structure' }], + ['Getting Started/intro.md', { title: 'Introduction' }], ], }) ); @@ -33,6 +34,22 @@ describe('getSidebar', () => { "label": "Eco-friendly docs", "type": "link", }, + { + "badge": undefined, + "collapsed": false, + "entries": [ + { + "attrs": {}, + "badge": undefined, + "href": "/getting-started/intro/", + "isCurrent": false, + "label": "Introduction", + "type": "link", + }, + ], + "label": "Getting Started", + "type": "group", + }, { "badge": undefined, "collapsed": false, @@ -152,6 +169,14 @@ describe('flattenSidebar', () => { "label": "Eco-friendly docs", "type": "link", }, + { + "attrs": {}, + "badge": undefined, + "href": "/getting-started/intro/", + "isCurrent": false, + "label": "Introduction", + "type": "link", + }, { "attrs": {}, "badge": undefined, @@ -182,9 +207,9 @@ describe('getPrevNextLinks', () => { "next": { "attrs": {}, "badge": undefined, - "href": "/guides/authoring-content/", + "href": "/getting-started/intro/", "isCurrent": false, - "label": "Authoring Markdown", + "label": "Introduction", "type": "link", }, "prev": { diff --git a/packages/starlight/__tests__/test-utils.ts b/packages/starlight/__tests__/test-utils.ts index 45dbde66..d6e3a711 100644 --- a/packages/starlight/__tests__/test-utils.ts +++ b/packages/starlight/__tests__/test-utils.ts @@ -28,7 +28,11 @@ function mockDoc( data: z.input, body = '' ): StarlightDocsCollectionEntry { - const slug = docsFilePath.replace(/\.[^\.]+$/, '').replace(/\/index$/, ''); + const slug = docsFilePath + .replace(/\.[^\.]+$/, '') + .replace(/\s/, '-') + .replace(/\/index$/, '') + .toLowerCase(); const doc: StarlightDocsCollectionEntry = { id: project.legacyCollections ? docsFilePath : slug, diff --git a/packages/starlight/utils/navigation.ts b/packages/starlight/utils/navigation.ts index 76608601..921dc2fe 100644 --- a/packages/starlight/utils/navigation.ts +++ b/packages/starlight/utils/navigation.ts @@ -1,5 +1,6 @@ import { AstroError } from 'astro/errors'; import config from 'virtual:starlight/user-config'; +import project from 'virtual:starlight/project-context'; import type { Badge, I18nBadge, I18nBadgeConfig } from '../schemas/badge'; import type { PrevNextLinkConfig } from '../schemas/prevNextLink'; import type { @@ -14,8 +15,9 @@ import { formatPath } from './format-path'; import { BuiltInDefaultLocale, pickLang } from './i18n'; import { ensureLeadingSlash, ensureTrailingSlash, stripLeadingAndTrailingSlashes } from './path'; import { getLocaleRoutes, routes, type Route } from './routing'; -import { localeToLang, slugToPathname } from './slugs'; +import { localeToLang, localizedId, slugToPathname } from './slugs'; import type { StarlightConfig } from './user-config'; +import { getCollectionPathFromRoot } from './collection'; const DirKey = Symbol('DirKey'); const SlugKey = Symbol('SlugKey'); @@ -108,7 +110,7 @@ function groupFromAutogenerateConfig( // Match against `foo/anything/else.md`. doc.id.startsWith(localeDir + '/') ); - const tree = treeify(dirDocs, localeDir); + const tree = treeify(dirDocs, locale, localeDir); const label = pickLang(item.translations, localeToLang(locale)) || item.label; return { type: 'group', @@ -205,8 +207,8 @@ function pathsMatch(pathA: string, pathB: string) { function getBreadcrumbs(path: string, baseDir: string): string[] { // Strip extension from path. const pathWithoutExt = stripExtension(path); - // Index paths will match `baseDir` but we still need to consider them as a single segment. - if (pathWithoutExt === baseDir) return [path]; + // Index paths will match `baseDir` and don’t include breadcrumbs. + if (pathWithoutExt === baseDir) return []; // Ensure base directory ends in a trailing slash. baseDir = ensureTrailingSlash(baseDir); // Strip base directory from path if present. @@ -218,16 +220,28 @@ function getBreadcrumbs(path: string, baseDir: string): string[] { } /** Turn a flat array of routes into a tree structure. */ -function treeify(routes: Route[], baseDir: string): Dir { +function treeify(routes: Route[], locale: string | undefined, baseDir: string): Dir { const treeRoot: Dir = makeDir(baseDir); + const collectionPathFromRoot = getCollectionPathFromRoot('docs', project); routes // Remove any entries that should be hidden .filter((doc) => !doc.entry.data.sidebar.hidden) + // Compute the path of each entry from the root of the collection ahead of time. + .map( + (doc) => + [ + project.legacyCollections + ? doc.id + : // For collections with a loader, use a localized filePath relative to the collection + localizedId(doc.entry.filePath.replace(`${collectionPathFromRoot}/`, ''), locale), + doc, + ] as const + ) // Sort by depth, to build the tree depth first. - .sort((a, b) => b.id.split('/').length - a.id.split('/').length) + .sort(([a], [b]) => b.split('/').length - a.split('/').length) // Build the tree - .forEach((doc) => { - const parts = getBreadcrumbs(doc.id, baseDir); + .forEach(([filePathFromContentDir, doc]) => { + const parts = getBreadcrumbs(filePathFromContentDir, baseDir); let currentNode = treeRoot; parts.forEach((part, index) => { @@ -374,7 +388,7 @@ function getIntermediateSidebarFromConfig( if (sidebarConfig) { return sidebarConfig.map((group) => configItemToEntry(group, pathname, locale, routes)); } else { - const tree = treeify(routes, locale || ''); + const tree = treeify(routes, locale, locale || ''); return sidebarFromDir(tree, pathname, locale, false); } } diff --git a/packages/starlight/utils/slugs.ts b/packages/starlight/utils/slugs.ts index c811c550..c6b0b739 100644 --- a/packages/starlight/utils/slugs.ts +++ b/packages/starlight/utils/slugs.ts @@ -82,7 +82,8 @@ export function localizedSlug(slug: string, locale: string | undefined): string } /** - * Convert a legacy collection entry ID to a different locale. + * Convert a legacy collection entry ID or filePath relative to the collection root to a different + * locale. * For example, passing an ID of `en/home.md` and a locale of `fr` results in `fr/home.md`. * An undefined locale is treated as the root locale, resulting in `home.md`. * @param id A collection entry ID -- cgit