diff --git a/src/layouts/DocsLayout.astro b/src/layouts/DocsLayout.astro index 0b8f0682..eb7409ac 100644 --- a/src/layouts/DocsLayout.astro +++ b/src/layouts/DocsLayout.astro @@ -10,6 +10,7 @@ import CopyPageDropdown from '../components/CopyPageDropdown.astro'; import PageFeedback from '../components/PageFeedback.tsx'; import GiscusComments from '../components/GiscusComments.tsx'; import FastNav from '../components/FastNav.astro'; +import { getFirstChildHrefForPath } from '../lib/navigation'; interface Props { frontmatter: { @@ -57,11 +58,19 @@ function breadcrumbHasPage(segments: string[], upTo: number): boolean { ); } -const breadcrumbs = pathSegments.map((segment, i) => ({ - name: segment.replace(/-/g, ' ').replace(/\b\w/g, (c: string) => c.toUpperCase()), - url: `https://docs.futureagi.com/${pathSegments.slice(0, i + 1).join('/')}`, - hasPage: breadcrumbHasPage(pathSegments, i), -})); +const breadcrumbs = pathSegments.map((segment, i) => { + const relPath = '/' + pathSegments.slice(0, i + 1).join('/'); + const hasPage = breadcrumbHasPage(pathSegments, i); + // If no page exists at this URL but children exist in nav, fall back to the + // first child so the breadcrumb segment stays clickable. + const fallbackHref = hasPage ? undefined : getFirstChildHrefForPath(relPath); + return { + name: segment.replace(/-/g, ' ').replace(/\b\w/g, (c: string) => c.toUpperCase()), + url: `https://docs.futureagi.com${relPath}`, + linkHref: hasPage ? relPath : fallbackHref, + hasLink: hasPage || Boolean(fallbackHref), + }; +}); // Override last breadcrumb with actual page title if (breadcrumbs.length > 0) { breadcrumbs[breadcrumbs.length - 1].name = frontmatter.title; @@ -98,8 +107,8 @@ if (breadcrumbs.length > 0) {
  • {i > 0 && /} {i < breadcrumbs.length - 1 ? ( - crumb.hasPage ? ( - {crumb.name} + crumb.hasLink && crumb.linkHref ? ( + {crumb.name} ) : ( {crumb.name} ) diff --git a/src/lib/navigation.ts b/src/lib/navigation.ts index f8c26b39..daacefd0 100644 --- a/src/lib/navigation.ts +++ b/src/lib/navigation.ts @@ -1286,6 +1286,34 @@ function matchesPath(items: NavItem[], normalizedPath: string): boolean { return false; } +// Walk nav items and return the first href found that starts with `prefix + '/'`. +// Used to make breadcrumb segments clickable when no page exists at the +// intermediate URL (e.g., `/docs/quickstart` has no page but child pages exist +// under `/docs/quickstart/*`). +function findFirstHrefUnder(items: NavItem[], prefix: string): string | undefined { + for (const item of items) { + if (item.href && item.href !== prefix && item.href.startsWith(prefix + '/')) { + return item.href; + } + if (item.items) { + const found = findFirstHrefUnder(item.items, prefix); + if (found) return found; + } + } + return undefined; +} + +export function getFirstChildHrefForPath(partialPath: string): string | undefined { + const prefix = partialPath.replace(/\/$/, ''); + for (const tab of tabNavigation) { + for (const group of tab.groups) { + const found = findFirstHrefUnder(group.items, prefix); + if (found) return found; + } + } + return undefined; +} + // Find the active group within the Docs tab based on current path export function getActiveGroup(currentPath: string): NavGroup | undefined { const docsTab = tabNavigation[0]; // Docs tab