diff --git a/e2e/fixtures/auto-nav-sidebar/doc/api/client-api/_meta.json b/e2e/fixtures/auto-nav-sidebar/doc/api/client-api/_meta.json index d6f2228e4..f0d31f3ee 100644 --- a/e2e/fixtures/auto-nav-sidebar/doc/api/client-api/_meta.json +++ b/e2e/fixtures/auto-nav-sidebar/doc/api/client-api/_meta.json @@ -1,9 +1,4 @@ [ - { - "type": "file", - "name": "index", - "label": "Client API Overview" - }, { "type": "file", "name": "api-runtime", diff --git a/e2e/fixtures/nested-overview/doc/_meta.json b/e2e/fixtures/nested-overview/doc/_meta.json index 9134f94c0..48c189727 100644 --- a/e2e/fixtures/nested-overview/doc/_meta.json +++ b/e2e/fixtures/nested-overview/doc/_meta.json @@ -1,7 +1,12 @@ [ { - "text": "Guide", - "link": "/level1/", - "activeMatch": "/level1/" + "text": "Basic", + "link": "/basic-level-1/", + "activeMatch": "/basic-level-1" + }, + { + "text": "Index Convention", + "link": "/index-convention-level-1/", + "activeMatch": "/index-convention-level-1" } ] diff --git a/e2e/fixtures/nested-overview/doc/basic-level-1/_meta.json b/e2e/fixtures/nested-overview/doc/basic-level-1/_meta.json new file mode 100644 index 000000000..705a5318b --- /dev/null +++ b/e2e/fixtures/nested-overview/doc/basic-level-1/_meta.json @@ -0,0 +1,8 @@ +[ + "index", + { + "type": "dir", + "label": "Level 2", + "name": "level-2" + } +] \ No newline at end of file diff --git a/e2e/fixtures/nested-overview/doc/level1/level2/index.mdx b/e2e/fixtures/nested-overview/doc/basic-level-1/index.mdx similarity index 60% rename from e2e/fixtures/nested-overview/doc/level1/level2/index.mdx rename to e2e/fixtures/nested-overview/doc/basic-level-1/index.mdx index 41fc3a5f6..309258797 100644 --- a/e2e/fixtures/nested-overview/doc/level1/level2/index.mdx +++ b/e2e/fixtures/nested-overview/doc/basic-level-1/index.mdx @@ -1,3 +1,4 @@ --- +title: Level 1 overview: true --- diff --git a/e2e/fixtures/nested-overview/doc/basic-level-1/level-2/_meta.json b/e2e/fixtures/nested-overview/doc/basic-level-1/level-2/_meta.json new file mode 100644 index 000000000..125184c0e --- /dev/null +++ b/e2e/fixtures/nested-overview/doc/basic-level-1/level-2/_meta.json @@ -0,0 +1,9 @@ +[ + "index", + "two", + { + "type": "dir", + "name": "level-3", + "label": "Level 3" + } +] diff --git a/e2e/fixtures/nested-overview/doc/level1/level2/level3/index.mdx b/e2e/fixtures/nested-overview/doc/basic-level-1/level-2/index.mdx similarity index 60% rename from e2e/fixtures/nested-overview/doc/level1/level2/level3/index.mdx rename to e2e/fixtures/nested-overview/doc/basic-level-1/level-2/index.mdx index 41fc3a5f6..aa9d3a4c7 100644 --- a/e2e/fixtures/nested-overview/doc/level1/level2/level3/index.mdx +++ b/e2e/fixtures/nested-overview/doc/basic-level-1/level-2/index.mdx @@ -1,3 +1,4 @@ --- +title: Level 2 overview: true --- diff --git a/e2e/fixtures/nested-overview/doc/basic-level-1/level-2/level-3/_meta.json b/e2e/fixtures/nested-overview/doc/basic-level-1/level-2/level-3/_meta.json new file mode 100644 index 000000000..b96ffa4b4 --- /dev/null +++ b/e2e/fixtures/nested-overview/doc/basic-level-1/level-2/level-3/_meta.json @@ -0,0 +1 @@ +["index", "three"] diff --git a/e2e/fixtures/nested-overview/doc/basic-level-1/level-2/level-3/index.mdx b/e2e/fixtures/nested-overview/doc/basic-level-1/level-2/level-3/index.mdx new file mode 100644 index 000000000..4964e1a46 --- /dev/null +++ b/e2e/fixtures/nested-overview/doc/basic-level-1/level-2/level-3/index.mdx @@ -0,0 +1,4 @@ +--- +title: Level 3 +overview: true +--- diff --git a/e2e/fixtures/nested-overview/doc/level1/level2/level3/three.mdx b/e2e/fixtures/nested-overview/doc/basic-level-1/level-2/level-3/three.mdx similarity index 100% rename from e2e/fixtures/nested-overview/doc/level1/level2/level3/three.mdx rename to e2e/fixtures/nested-overview/doc/basic-level-1/level-2/level-3/three.mdx diff --git a/e2e/fixtures/nested-overview/doc/level1/level2/two.mdx b/e2e/fixtures/nested-overview/doc/basic-level-1/level-2/two.mdx similarity index 100% rename from e2e/fixtures/nested-overview/doc/level1/level2/two.mdx rename to e2e/fixtures/nested-overview/doc/basic-level-1/level-2/two.mdx diff --git a/e2e/fixtures/nested-overview/doc/index-convention-level-1/_meta.json b/e2e/fixtures/nested-overview/doc/index-convention-level-1/_meta.json new file mode 100644 index 000000000..212ea50b7 --- /dev/null +++ b/e2e/fixtures/nested-overview/doc/index-convention-level-1/_meta.json @@ -0,0 +1,8 @@ +[ + "index", + { + "type": "dir", + "label": "Level 2", + "name": "level-2" + } +] diff --git a/e2e/fixtures/nested-overview/doc/index-convention-level-1/index.mdx b/e2e/fixtures/nested-overview/doc/index-convention-level-1/index.mdx new file mode 100644 index 000000000..309258797 --- /dev/null +++ b/e2e/fixtures/nested-overview/doc/index-convention-level-1/index.mdx @@ -0,0 +1,4 @@ +--- +title: Level 1 +overview: true +--- diff --git a/e2e/fixtures/nested-overview/doc/index-convention-level-1/level-2/_meta.json b/e2e/fixtures/nested-overview/doc/index-convention-level-1/level-2/_meta.json new file mode 100644 index 000000000..0a84e05b5 --- /dev/null +++ b/e2e/fixtures/nested-overview/doc/index-convention-level-1/level-2/_meta.json @@ -0,0 +1,8 @@ +[ + "two", + { + "type": "dir", + "name": "level-3", + "label": "Level 3" + } +] diff --git a/e2e/fixtures/nested-overview/doc/index-convention-level-1/level-2/index.mdx b/e2e/fixtures/nested-overview/doc/index-convention-level-1/level-2/index.mdx new file mode 100644 index 000000000..aa9d3a4c7 --- /dev/null +++ b/e2e/fixtures/nested-overview/doc/index-convention-level-1/level-2/index.mdx @@ -0,0 +1,4 @@ +--- +title: Level 2 +overview: true +--- diff --git a/e2e/fixtures/nested-overview/doc/index-convention-level-1/level-2/level-3/_meta.json b/e2e/fixtures/nested-overview/doc/index-convention-level-1/level-2/level-3/_meta.json new file mode 100644 index 000000000..54bdf9a8b --- /dev/null +++ b/e2e/fixtures/nested-overview/doc/index-convention-level-1/level-2/level-3/_meta.json @@ -0,0 +1 @@ +["three", "four"] diff --git a/e2e/fixtures/nested-overview/doc/index-convention-level-1/level-2/level-3/four.mdx b/e2e/fixtures/nested-overview/doc/index-convention-level-1/level-2/level-3/four.mdx new file mode 100644 index 000000000..0adae572c --- /dev/null +++ b/e2e/fixtures/nested-overview/doc/index-convention-level-1/level-2/level-3/four.mdx @@ -0,0 +1 @@ +# Four diff --git a/e2e/fixtures/nested-overview/doc/level1/index.mdx b/e2e/fixtures/nested-overview/doc/index-convention-level-1/level-2/level-3/index.mdx similarity index 100% rename from e2e/fixtures/nested-overview/doc/level1/index.mdx rename to e2e/fixtures/nested-overview/doc/index-convention-level-1/level-2/level-3/index.mdx diff --git a/e2e/fixtures/nested-overview/doc/index-convention-level-1/level-2/level-3/three.mdx b/e2e/fixtures/nested-overview/doc/index-convention-level-1/level-2/level-3/three.mdx new file mode 100644 index 000000000..fac55f75e --- /dev/null +++ b/e2e/fixtures/nested-overview/doc/index-convention-level-1/level-2/level-3/three.mdx @@ -0,0 +1 @@ +# Three diff --git a/e2e/fixtures/nested-overview/doc/index-convention-level-1/level-2/two.mdx b/e2e/fixtures/nested-overview/doc/index-convention-level-1/level-2/two.mdx new file mode 100644 index 000000000..2ef7ae0ba --- /dev/null +++ b/e2e/fixtures/nested-overview/doc/index-convention-level-1/level-2/two.mdx @@ -0,0 +1 @@ +# Two diff --git a/e2e/tests/auto-nav-sidebar.test.ts b/e2e/tests/auto-nav-sidebar.test.ts index 2d740439d..f02cce16c 100644 --- a/e2e/tests/auto-nav-sidebar.test.ts +++ b/e2e/tests/auto-nav-sidebar.test.ts @@ -53,7 +53,6 @@ test.describe('Auto nav and sidebar test', async () => { 'Build Config', 'Extname Config', 'Nested', - 'Client API Overview', 'Runtime API', 'Components', 'Commands', @@ -90,7 +89,16 @@ test.describe('Auto nav and sidebar test', async () => { const h2 = await page.$$('.overview-index h2'); const h2Texts = await Promise.all(h2.map(element => element.textContent())); - expect(h2Texts.join(',')).toEqual(['Config'].join(',')); + expect(h2Texts.join(',')).toEqual( + [ + 'Basic Config', + 'Theme Config', + 'Front Matter Config', + 'Build Config', + 'Extname Config', + 'Nested', + ].join(','), + ); const h3 = await page.$$('.overview-group_8f375 h3'); const h3Texts = await Promise.all(h3.map(element => element.textContent())); @@ -101,7 +109,7 @@ test.describe('Auto nav and sidebar test', async () => { 'Front Matter Config', 'Build Config', 'Extname Config', - 'Nested', + 'Nested Config', ].join(','), ); @@ -117,6 +125,7 @@ test.describe('Auto nav and sidebar test', async () => { 'Default Config', 'markdown', 'markdown.remarkPlugins', + 'Nested H2', ].join(','), ); }); @@ -124,7 +133,7 @@ test.describe('Auto nav and sidebar test', async () => { test('Should load subpage API Overview correctly - index.md', async ({ page, }) => { - await page.goto(`http://localhost:${appPort}/api/client-api/index.html`, { + await page.goto(`http://localhost:${appPort}/api/client-api.html`, { waitUntil: 'networkidle', }); diff --git a/e2e/tests/nested-overview.test.ts b/e2e/tests/nested-overview.test.ts index 85954dafb..506389e30 100644 --- a/e2e/tests/nested-overview.test.ts +++ b/e2e/tests/nested-overview.test.ts @@ -22,40 +22,43 @@ test.describe('Nested overview page', async () => { test('Should load nested overview page correctly - level 1', async ({ page, }) => { - await page.goto(`http://localhost:${appPort}/level1/index.html`, { + await page.goto(`http://localhost:${appPort}/basic-level-1/index.html`, { waitUntil: 'networkidle', }); const h2 = await page.$$('.overview-index h2'); const h2Texts = await Promise.all(h2.map(element => element.textContent())); - expect(h2Texts.join(',')).toEqual(['level2'].join(',')); + expect(h2Texts.join(',')).toEqual(['Level 2'].join(',')); const h3 = await page.$$('.overview-group_8f375 h3'); const h3Texts = await Promise.all(h3.map(element => element.textContent())); - expect(h3Texts.join(',')).toEqual(['index', 'level3', 'two'].join(',')); + expect(h3Texts.join(',')).toEqual(['Level 2', 'two', 'Level 3'].join(',')); }); test('Should load nested overview page correctly - level 2', async ({ page, }) => { - await page.goto(`http://localhost:${appPort}/level1/level2/index.html`, { - waitUntil: 'networkidle', - }); + await page.goto( + `http://localhost:${appPort}/basic-level-1/level-2/index.html`, + { + waitUntil: 'networkidle', + }, + ); const h2 = await page.$$('.overview-index h2'); const h2Texts = await Promise.all(h2.map(element => element.textContent())); - expect(h2Texts.join(',')).toEqual(['level3', 'two'].join(',')); + expect(h2Texts.join(',')).toEqual(['two', 'Level 3'].join(',')); const h3 = await page.$$('.overview-group_8f375 h3'); const h3Texts = await Promise.all(h3.map(element => element.textContent())); - expect(h3Texts.join(',')).toEqual(['index', 'three', 'two'].join(',')); + expect(h3Texts.join(',')).toEqual(['two', 'Level 3', 'three'].join(',')); }); test('Should load nested overview page correctly - level 3', async ({ page, }) => { await page.goto( - `http://localhost:${appPort}/level1/level2/level3/index.html`, + `http://localhost:${appPort}/basic-level-1/level-2/level-3/index.html`, { waitUntil: 'networkidle', }, diff --git a/packages/theme-default/src/components/Overview/index.tsx b/packages/theme-default/src/components/Overview/index.tsx index 4dc28cd14..8db5c072b 100644 --- a/packages/theme-default/src/components/Overview/index.tsx +++ b/packages/theme-default/src/components/Overview/index.tsx @@ -18,6 +18,7 @@ import { useLocaleSiteData, } from '../../logic'; import styles from './index.module.scss'; +import { findItemByRoutePath } from './utils'; interface GroupItem { text?: string; @@ -133,36 +134,6 @@ export function Overview(props: { return ''; }; - const removeIndex = (link: string) => { - if (link.endsWith('/index')) { - return link.slice(0, -5); - } - return link; - }; - - const findItemByRoutePath = (items, routePath, originalItems) => { - for (const item of items) { - if (withBase(item.link) === routePath) { - return [item]; - } - - if (removeIndex(withBase(item.link)) === routePath) { - return items; - } - - if (item.items) { - const foundItem = findItemByRoutePath( - item.items, - routePath, - originalItems, - ); - if (foundItem) { - return foundItem; - } - } - } - return originalItems; - }; const { pages } = siteData; const overviewModules = pages.filter(page => subFilter(page.routePath)); let { items: overviewSidebarGroups } = useSidebarData() as { @@ -181,7 +152,6 @@ export function Overview(props: { overviewSidebarGroups = findItemByRoutePath( overviewSidebarGroups, routePath, - overviewSidebarGroups, ); } @@ -227,8 +197,11 @@ export function Overview(props: { customGroups ?? useMemo(() => { const group = overviewSidebarGroups - .filter(sidebarGroup => { - if ('items' in sidebarGroup && sidebarGroup.items) { + .filter(normalizedSidebarGroup => { + const sidebarGroup = normalizedSidebarGroup as NormalizedSidebarGroup; + if (Array.isArray(sidebarGroup?.items)) { + console.log(sidebarGroup, 2222); + return ( sidebarGroup.items.filter(item => subFilter(getChildLink(item))) .length > 0 @@ -242,16 +215,13 @@ export function Overview(props: { } return false; }) - .map(sidebarGroup => { + .map(normalizedSidebarGroup => { + const sidebarGroup = normalizedSidebarGroup as NormalizedSidebarGroup; let items = []; - if ((sidebarGroup as NormalizedSidebarGroup)?.items) { - items = (sidebarGroup as NormalizedSidebarGroup)?.items + if (sidebarGroup?.items) { + items = sidebarGroup?.items ?.map(item => - normalizeSidebarItem( - item, - sidebarGroup as NormalizedSidebarGroup, - frontmatter, - ), + normalizeSidebarItem(item, sidebarGroup, frontmatter), ) .filter(Boolean); } else if (isSingleFile(sidebarGroup)) { diff --git a/packages/theme-default/src/components/Overview/utils.ts b/packages/theme-default/src/components/Overview/utils.ts new file mode 100644 index 000000000..d0ef1df93 --- /dev/null +++ b/packages/theme-default/src/components/Overview/utils.ts @@ -0,0 +1,88 @@ +import { + type NormalizedSidebarGroup, + type SidebarDivider, + type SidebarItem, + withBase, +} from '@rspress/shared'; +import { isSidebarDivider } from '../Sidebar/utils'; + +function removeIndex(link: string) { + if (link.endsWith('/index')) { + return link.slice(0, -5); + } + return link; +} + +/** + * @zh_CN 如果 index 在 sidebar items 中, 则返回所有平级 item, 如果 index 在 dir 上, 则返回 items + * @example + */ +export function findItemByRoutePath( + items: (SidebarItem | NormalizedSidebarGroup | SidebarDivider)[], + routePath: string, +): (SidebarItem | NormalizedSidebarGroup)[] { + function isRoutePathMatch( + item: SidebarItem | NormalizedSidebarGroup | SidebarDivider, + ) { + if (isSidebarDivider(item)) { + return false; + } + const withBaseUrl = withBase(item.link); + const removeIndexUrl = removeIndex(withBaseUrl); + const removeBackSlashedRoutePath = routePath.replace(/\/$/, ''); + return ( + // FIXME: 😅 we should refactor all the path logic, /index.html / or no /, l + withBaseUrl === routePath || + removeIndexUrl === routePath || + withBaseUrl === removeBackSlashedRoutePath || + removeIndexUrl === removeBackSlashedRoutePath + ); + } + + const matchRoutePathItemIndex = items.findIndex(item => { + return isRoutePathMatch(item); + }); + + if (matchRoutePathItemIndex === -1) { + return items + .map(item => { + if (!('items' in item)) { + return []; + } + return findItemByRoutePath(item.items, routePath); + }) + .flat(); + } + + const matchRoutePathItem = items[matchRoutePathItemIndex] as + | SidebarItem + | NormalizedSidebarGroup; + + const isArray = (i: unknown): i is Array => + Array.isArray(i) && i.length >= 1; + + // 1. if isDir(item) return item.items + if ('items' in matchRoutePathItem && isArray(matchRoutePathItem.items)) { + // 2. if isDir(item) && item.items is all files, style is different + if (matchRoutePathItem.items.every(item => !('items' in item))) { + return [matchRoutePathItem]; + } + + return matchRoutePathItem.items.filter(item => !isSidebarDivider(item)) as ( + | SidebarItem + | NormalizedSidebarGroup + )[]; + } + + // 3. if matchRoutePathItem is a item, return other items in same level (/api/index.md is in the child sidebar) + const result = [...items]; + if (!('items' in matchRoutePathItem)) { + result.splice(matchRoutePathItemIndex, 1); + } + const res = result.filter(item => !isSidebarDivider(item)) as ( + | SidebarItem + | NormalizedSidebarGroup + )[]; + + return res; +} diff --git a/packages/theme-default/src/components/Sidebar/SidebarGroup.tsx b/packages/theme-default/src/components/Sidebar/SidebarGroup.tsx index 622a7c239..ec342b203 100644 --- a/packages/theme-default/src/components/Sidebar/SidebarGroup.tsx +++ b/packages/theme-default/src/components/Sidebar/SidebarGroup.tsx @@ -11,11 +11,8 @@ import { Tag } from '@theme'; import styles from './index.module.scss'; import { SidebarItem as SidebarItemComp } from './SidebarItem'; import { SidebarDivider } from './SidebarDivider'; -import { - highlightTitleStyle, - isSidebarDivider, - type SidebarItemProps, -} from '.'; +import { highlightTitleStyle, type SidebarItemProps } from '.'; +import { isSidebarDivider } from './utils'; import { SvgWrapper } from '../SvgWrapper'; import { renderInlineMarkdown } from '../../logic'; diff --git a/packages/theme-default/src/components/Sidebar/index.tsx b/packages/theme-default/src/components/Sidebar/index.tsx index 314088088..5ad723197 100644 --- a/packages/theme-default/src/components/Sidebar/index.tsx +++ b/packages/theme-default/src/components/Sidebar/index.tsx @@ -6,7 +6,6 @@ import { type SidebarItem as ISidebarItem, type SidebarDivider as ISidebarDivider, type SidebarSectionHeader as ISidebarSectionHeader, - isExternalUrl, } from '@rspress/shared'; import { routes } from 'virtual-routes'; import { matchRoutes, useLocation, removeBase } from '@rspress/runtime'; @@ -19,36 +18,11 @@ import type { UISwitchResult } from '../../logic/useUISwitch'; import { SidebarSectionHeader } from './SidebarSectionHeader'; import styles from './index.module.scss'; - -export const isSidebarDivider = ( - item: - | NormalizedSidebarGroup - | ISidebarItem - | ISidebarDivider - | ISidebarSectionHeader, -): item is ISidebarDivider => { - return 'dividerType' in item; -}; - -export const isSidebarSectionHeader = ( - item: - | NormalizedSidebarGroup - | ISidebarItem - | ISidebarDivider - | ISidebarSectionHeader, -): item is ISidebarSectionHeader => { - return 'sectionHeaderText' in item; -}; - -export const isSideBarCustomLink = ( - item: - | NormalizedSidebarGroup - | ISidebarItem - | ISidebarDivider - | ISidebarSectionHeader, -) => { - return 'link' in item && isExternalUrl(item.link); -}; +import { + isSideBarCustomLink, + isSidebarDivider, + isSidebarSectionHeader, +} from './utils'; export interface SidebarItemProps { id: string; diff --git a/packages/theme-default/src/components/Sidebar/utils.ts b/packages/theme-default/src/components/Sidebar/utils.ts new file mode 100644 index 000000000..c52ebefd9 --- /dev/null +++ b/packages/theme-default/src/components/Sidebar/utils.ts @@ -0,0 +1,37 @@ +import { + isExternalUrl, + type NormalizedSidebarGroup, + type SidebarItem as ISidebarItem, + type SidebarDivider as ISidebarDivider, + type SidebarSectionHeader as ISidebarSectionHeader, +} from '@rspress/shared'; + +export const isSidebarDivider = ( + item: + | NormalizedSidebarGroup + | ISidebarItem + | ISidebarDivider + | ISidebarSectionHeader, +): item is ISidebarDivider => { + return 'dividerType' in item; +}; + +export const isSidebarSectionHeader = ( + item: + | NormalizedSidebarGroup + | ISidebarItem + | ISidebarDivider + | ISidebarSectionHeader, +): item is ISidebarSectionHeader => { + return 'sectionHeaderText' in item; +}; + +export const isSideBarCustomLink = ( + item: + | NormalizedSidebarGroup + | ISidebarItem + | ISidebarDivider + | ISidebarSectionHeader, +) => { + return 'link' in item && isExternalUrl(item.link); +};