diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 6fcfddca4..6c36dcd06 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -26,7 +26,8 @@ export { matchRoutes, BrowserRouter, useSearchParams, + matchPath, } from 'react-router-dom'; -export { pathnameToRouteService } from './route'; +export { pathnameToRouteService, normalizeRoutePath } from './route'; export { Helmet } from 'react-helmet-async'; export { NoSSR } from './NoSSR'; diff --git a/packages/runtime/src/route.ts b/packages/runtime/src/route.ts index dc189e0b8..110c1aa23 100644 --- a/packages/runtime/src/route.ts +++ b/packages/runtime/src/route.ts @@ -2,7 +2,7 @@ import type { Route } from '@rspress/shared'; import { routes } from '__VIRTUAL_ROUTES__'; import { matchRoutes } from 'react-router-dom'; -function normalizeRoutePath(routePath: string) { +export function normalizeRoutePath(routePath: string) { return decodeURIComponent(routePath) .replace(/\.html$/, '') .replace(/\/index$/, '/'); diff --git a/packages/theme-default/src/components/Overview/index.tsx b/packages/theme-default/src/components/Overview/index.tsx index a35161c6d..6f0117b12 100644 --- a/packages/theme-default/src/components/Overview/index.tsx +++ b/packages/theme-default/src/components/Overview/index.tsx @@ -153,9 +153,10 @@ export function Overview(props: { const { pages } = siteData; const overviewModules = pages.filter(page => subFilter(page.routePath)); - let { items: overviewSidebarGroups } = useSidebarData() as { - items: (NormalizedSidebarGroup | SidebarItem)[]; - }; + let overviewSidebarGroups = useSidebarData() as ( + | NormalizedSidebarGroup + | SidebarItem + )[]; const { overview: { diff --git a/packages/theme-default/src/components/Sidebar/index.tsx b/packages/theme-default/src/components/Sidebar/index.tsx index 384c8876a..b1cf4a367 100644 --- a/packages/theme-default/src/components/Sidebar/index.tsx +++ b/packages/theme-default/src/components/Sidebar/index.tsx @@ -70,7 +70,7 @@ export function Sidebar(props: Props) { props; const { pathname: rawPathname } = useLocation(); - const { items: rawSidebarData } = useSidebarData(); + const rawSidebarData = useSidebarData(); const [sidebarData, setSidebarData] = useState(() => { return rawSidebarData.filter(Boolean).flat(); }); diff --git a/packages/theme-default/src/logic/getSidebarDataGroup.test.ts b/packages/theme-default/src/logic/getSidebarDataGroup.test.ts index a9ce2bfbd..c93e3b53e 100644 --- a/packages/theme-default/src/logic/getSidebarDataGroup.test.ts +++ b/packages/theme-default/src/logic/getSidebarDataGroup.test.ts @@ -1,22 +1,22 @@ import { describe, expect, it, vi } from 'vitest'; -import { getSidebarDataGroup } from './getSidebarDataGroup'; +import { getSidebarDataGroup, isActive } from './getSidebarDataGroup'; -vi.mock('@rspress/runtime', () => { +vi.mock('virtual-i18n-text', () => { + return { default: {} }; +}); + +vi.mock('virtual-site-data', () => { return { - withBase: (arg0: string) => arg0, - pathnameToRouteService: (arg0: string) => { - const map: Record = { - '/guide/getting-started': '/guide/getting-started', - '/api/react.use': '/api/react.use', - '/api/react.use.html': '/api/react.use', - }; - return { - path: map[arg0], - }; + default: { + base: '/', }, }; }); +vi.mock('__VIRTUAL_ROUTES__', () => { + return { routes: [] }; +}); + describe('getSidebarDataGroup', () => { it('when using "/"', async () => { expect( @@ -33,15 +33,12 @@ describe('getSidebarDataGroup', () => { '/guide/getting-started', ), ).toMatchInlineSnapshot(` - { - "group": "Getting Started", - "items": [ - { - "link": "/guide/getting-started", - "text": "Getting Started", - }, - ], - } + [ + { + "link": "/guide/getting-started", + "text": "Getting Started", + }, + ] `); }); @@ -54,15 +51,36 @@ describe('getSidebarDataGroup', () => { '/api/react.use.html', ), ).toMatchInlineSnapshot(` - { - "group": "react.use", - "items": [ - { - "link": "/api/react.use", - "text": "react.use", - }, - ], - } + [ + { + "link": "/api/react.use", + "text": "react.use", + }, + ] `); }); }); + +describe('isActive', () => { + it('pass cases', () => { + const routes = [ + '/api/config', + '/api/config.html', + '/api/config/', + '/api/config/index', + '/api/config/index.html', + ]; + + for (const route of routes) { + for (const route2 of routes) { + expect(isActive(route, route2)).toBeTruthy(); + } + } + }); + + it('failed cases', () => { + expect(isActive('/api/config', '/api/config2')).toBeFalsy(); + expect(isActive('/api/config', '/api/config/config2')).toBeFalsy(); + expect(isActive('/api/config/index', '/api/config/config2')).toBeFalsy(); + }); +}); diff --git a/packages/theme-default/src/logic/getSidebarDataGroup.ts b/packages/theme-default/src/logic/getSidebarDataGroup.ts index 786f44821..bee99193d 100644 --- a/packages/theme-default/src/logic/getSidebarDataGroup.ts +++ b/packages/theme-default/src/logic/getSidebarDataGroup.ts @@ -1,4 +1,8 @@ -import { pathnameToRouteService, withBase } from '@rspress/runtime'; +import { + normalizeRoutePath, + matchPath as reactRouterDomMatchPath, + withBase, +} from '@rspress/runtime'; import { type NormalizedSidebar, type NormalizedSidebarGroup, @@ -8,12 +12,6 @@ import { } from '@rspress/shared'; import type { SidebarData } from '../components'; -export interface SidebarDataGroup { - // The group name for the sidebar - group: string; - items: SidebarData; -} - /** * match the sidebar key in user config * @param pattern /zh/guide @@ -53,13 +51,13 @@ export const matchPath = ( * @returns */ export function isActive(itemLink: string, currentPathname: string): boolean { - const linkMatchedRoute = pathnameToRouteService(withBase(itemLink)); - const pathnameMatchedRoute = pathnameToRouteService(currentPathname); - return Boolean( - linkMatchedRoute && - pathnameMatchedRoute && - linkMatchedRoute.path === pathnameMatchedRoute.path, + const normalizedItemLink = normalizeRoutePath(withBase(itemLink)); + const normalizedCurrentPathname = normalizeRoutePath(currentPathname); + const linkMatched = reactRouterDomMatchPath( + normalizedItemLink, + normalizedCurrentPathname, ); + return linkMatched !== null; } /** @@ -113,7 +111,7 @@ const match = ( export const getSidebarDataGroup = ( sidebar: NormalizedSidebar, currentPathname: string, -): SidebarDataGroup => { +): SidebarData => { /** * why sort? * { @@ -130,15 +128,8 @@ export const getSidebarDataGroup = ( for (const name of navRoutes) { if (matchPath(name, currentPathname)) { const sidebarGroup = sidebar[name]; - const group = sidebarGroup.find(item => match(item, currentPathname)); - return { - group: group && 'text' in group ? group.text : '', - items: sidebarGroup, - }; + return sidebarGroup; } } - return { - group: 'Documentation', - items: [], - }; + return []; }; diff --git a/packages/theme-default/src/logic/usePrevNextPage.ts b/packages/theme-default/src/logic/usePrevNextPage.ts index 990f16f7a..89bca2a1c 100644 --- a/packages/theme-default/src/logic/usePrevNextPage.ts +++ b/packages/theme-default/src/logic/usePrevNextPage.ts @@ -4,7 +4,7 @@ import { useSidebarData } from './useSidebarData'; export function usePrevNextPage() { const { pathname } = useLocation(); - const { items } = useSidebarData(); + const items = useSidebarData(); const flattenTitles: SidebarItem[] = []; const walk = (sidebarItem: NormalizedSidebarGroup | SidebarItem) => { diff --git a/packages/theme-default/src/logic/useSidebarData.ts b/packages/theme-default/src/logic/useSidebarData.ts index a3855471f..5ced1536e 100644 --- a/packages/theme-default/src/logic/useSidebarData.ts +++ b/packages/theme-default/src/logic/useSidebarData.ts @@ -1,17 +1,15 @@ import { useLocation } from '@rspress/runtime'; import { useMemo } from 'react'; -import { - type SidebarDataGroup, - getSidebarDataGroup, -} from './getSidebarDataGroup'; +import type { SidebarData } from '../components/Sidebar'; +import { getSidebarDataGroup } from './getSidebarDataGroup'; import { useLocaleSiteData } from './useLocaleSiteData'; -export function useSidebarData(): SidebarDataGroup { +export function useSidebarData(): SidebarData { const { sidebar } = useLocaleSiteData(); const { pathname: rawPathname } = useLocation(); const pathname = decodeURIComponent(rawPathname); - const sidebarData = useMemo(() => { + const sidebarData: SidebarData = useMemo(() => { return getSidebarDataGroup(sidebar, pathname); }, [sidebar, pathname]);