Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fifty-trees-approve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/starlight': patch
---

Fix current page highlight in sidebar for URLs with no trailing slash
7 changes: 7 additions & 0 deletions packages/starlight/__tests__/basics/navigation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ describe('getSidebar', () => {
}
});

test('ignore trailing slashes when marking current path with isCurrent', () => {
const pathWithoutTrailingSlash = '/environmental-impact';
const items = flattenSidebar(getSidebar(pathWithoutTrailingSlash, undefined));
const currentItems = items.filter((item) => item.type === 'link' && item.isCurrent);
expect(currentItems).toMatchObject([{ href: `${pathWithoutTrailingSlash}/`, type: 'link' }]);
});

test('nests files in subdirectory in group when autogenerating', () => {
const sidebar = getSidebar('/', undefined);
expect(sidebar.every((item) => item.type === 'group' || !item.href.startsWith('/guides/')));
Expand Down
13 changes: 4 additions & 9 deletions packages/starlight/utils/base.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import { stripLeadingAndTrailingSlashes, stripTrailingSlash } from './path';

const base = stripTrailingSlash(import.meta.env.BASE_URL);

/** Get the a root-relative URL path with the site’s `base` prefixed. */
export function pathWithBase(path: string) {
path = stripLeadingSlash(stripTrailingSlash(path));
path = stripLeadingAndTrailingSlashes(path);
return path ? base + '/' + path + '/' : base + '/';
}

/** Get the a root-relative file URL path with the site’s `base` prefixed. */
export function fileWithBase(path: string) {
path = stripLeadingSlash(stripTrailingSlash(path));
path = stripLeadingAndTrailingSlashes(path);
return path ? base + '/' + path : base;
}

function stripLeadingSlash(path: string) {
return path.replace(/^\//, '');
}
function stripTrailingSlash(path: string) {
return path.replace(/\/$/, '');
}
10 changes: 2 additions & 8 deletions packages/starlight/utils/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { pickLang } from './i18n';
import { getLocaleRoutes, type Route } from './routing';
import { localeToLang, slugToPathname } from './slugs';
import type { AutoSidebarGroup, SidebarItem, SidebarLinkItem } from './user-config';
import { ensureLeadingAndTrailingSlashes, ensureTrailingSlash } from './path';

const DirKey = Symbol('DirKey');

Expand Down Expand Up @@ -98,13 +99,6 @@ function groupFromAutogenerateConfig(
/** Check if a string starts with one of `http://` or `https://`. */
const isAbsolute = (link: string) => /^https?:\/\//.test(link);

/** Ensure the passed path starts and ends with trailing slashes. */
function ensureLeadingAndTrailingSlashes(href: string): string {
if (href[0] !== '/') href = '/' + href;
if (href[href.length - 1] !== '/') href += '/';
return href;
}

/** Create a link entry from a user config object. */
function linkFromConfig(
item: SidebarLinkItem,
Expand All @@ -124,7 +118,7 @@ function linkFromConfig(
/** Create a link entry. */
function makeLink(href: string, label: string, currentPathname: string): Link {
if (!isAbsolute(href)) href = pathWithBase(href);
const isCurrent = href === currentPathname;
const isCurrent = href === ensureTrailingSlash(currentPathname);
return { type: 'link', label, href, isCurrent };
}

Expand Down
37 changes: 37 additions & 0 deletions packages/starlight/utils/path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/** Ensure the passed path starts with a leading slash. */
export function ensureLeadingSlash(href: string): string {
if (href[0] !== '/') href = '/' + href;
return href;
}

/** Ensure the passed path ends with a trailing slash. */
export function ensureTrailingSlash(href: string): string {
if (href[href.length - 1] !== '/') href += '/';
return href;
}

/** Ensure the passed path starts and ends with slashes. */
export function ensureLeadingAndTrailingSlashes(href: string): string {
href = ensureLeadingSlash(href);
href = ensureTrailingSlash(href);
return href;
}

/** Ensure the passed path does not start with a leading slash. */
export function stripLeadingSlash(href: string) {
if (href[0] === '/') href = href.slice(1);
return href;
}

/** Ensure the passed path does not end with a trailing slash. */
export function stripTrailingSlash(href: string) {
if (href[href.length - 1] === '/') href = href.slice(0, -1);
return href;
}

/** Ensure the passed path does not start and end with slashes. */
export function stripLeadingAndTrailingSlashes(href: string): string {
href = stripLeadingSlash(href);
href = stripTrailingSlash(href);
return href;
}