From 52884d9d4b3ad294f4c4fcab637c4e07c80dde3a Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Fri, 4 Aug 2023 21:58:59 +0530 Subject: [PATCH] feat(theme): allow setting base path in sidebar items (#2734) --- .../theme-default/support/sidebar.test.ts | 34 ++-- docs/.vitepress/config.ts | 153 ++++++------------ src/client/theme-default/support/sidebar.ts | 46 +++--- types/default-theme.d.ts | 7 +- 4 files changed, 103 insertions(+), 137 deletions(-) diff --git a/__tests__/unit/client/theme-default/support/sidebar.test.ts b/__tests__/unit/client/theme-default/support/sidebar.test.ts index 5afdb95e..f33dcbd9 100644 --- a/__tests__/unit/client/theme-default/support/sidebar.test.ts +++ b/__tests__/unit/client/theme-default/support/sidebar.test.ts @@ -28,15 +28,19 @@ describe('client/theme-default/support/sidebar', () => { } test('gets `/` sidebar', () => { - expect(getSidebar(normalSidebar, '/')).toBe(root) + expect(getSidebar(normalSidebar, '/')).toStrictEqual(root) }) test('gets `/multi-sidebar/` sidebar', () => { - expect(getSidebar(normalSidebar, '/multi-sidebar/')).toBe(another) + expect(getSidebar(normalSidebar, '/multi-sidebar/')).toStrictEqual( + another + ) }) test('gets `/` sidebar again', () => { - expect(getSidebar(normalSidebar, '/some-entry.html')).toBe(root) + expect(getSidebar(normalSidebar, '/some-entry.html')).toStrictEqual( + root + ) }) }) @@ -47,15 +51,19 @@ describe('client/theme-default/support/sidebar', () => { } test('gets `/` sidebar', () => { - expect(getSidebar(reversedSidebar, '/')).toBe(root) + expect(getSidebar(reversedSidebar, '/')).toStrictEqual(root) }) test('gets `/multi-sidebar/` sidebar', () => { - expect(getSidebar(reversedSidebar, '/multi-sidebar/')).toBe(another) + expect(getSidebar(reversedSidebar, '/multi-sidebar/')).toStrictEqual( + another + ) }) test('gets `/` sidebar again', () => { - expect(getSidebar(reversedSidebar, '/some-entry.html')).toBe(root) + expect(getSidebar(reversedSidebar, '/some-entry.html')).toStrictEqual( + root + ) }) }) @@ -74,19 +82,25 @@ describe('client/theme-default/support/sidebar', () => { } test('gets `/` sidebar', () => { - expect(getSidebar(nestedSidebar, '/')).toBe(root) + expect(getSidebar(nestedSidebar, '/')).toStrictEqual(root) }) test('gets `/multi-sidebar/` sidebar', () => { - expect(getSidebar(nestedSidebar, '/multi-sidebar/')).toBe(another) + expect(getSidebar(nestedSidebar, '/multi-sidebar/')).toStrictEqual( + another + ) }) test('gets `/multi-sidebar/nested/` sidebar', () => { - expect(getSidebar(nestedSidebar, '/multi-sidebar/nested/')).toBe(nested) + expect( + getSidebar(nestedSidebar, '/multi-sidebar/nested/') + ).toStrictEqual(nested) }) test('gets `/` sidebar again', () => { - expect(getSidebar(nestedSidebar, '/some-entry.html')).toBe(root) + expect(getSidebar(nestedSidebar, '/some-entry.html')).toStrictEqual( + root + ) }) }) }) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index d1ae2feb..08e99329 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -1,5 +1,5 @@ import { createRequire } from 'module' -import { defineConfig } from 'vitepress' +import { defineConfig, type DefaultTheme } from 'vitepress' const require = createRequire(import.meta.url) const pkg = require('vitepress/package.json') @@ -36,8 +36,8 @@ export default defineConfig({ nav: nav(), sidebar: { - '/guide/': sidebarGuide(), - '/reference/': sidebarReference() + '/guide/': { base: '/guide/', items: sidebarGuide() }, + '/reference/': { base: '/reference/', items: sidebarReference() } }, editLink: { @@ -70,9 +70,13 @@ export default defineConfig({ } }) -function nav() { +function nav(): DefaultTheme.NavItem[] { return [ - { text: 'Guide', link: '/guide/what-is-vitepress', activeMatch: '/guide/' }, + { + text: 'Guide', + link: '/guide/what-is-vitepress', + activeMatch: '/guide/' + }, { text: 'Reference', link: '/reference/site-config', @@ -94,142 +98,79 @@ function nav() { ] } -function sidebarGuide() { +/* prettier-ignore */ +function sidebarGuide(): DefaultTheme.SidebarItem[] { return [ { text: 'Introduction', collapsed: false, items: [ - { text: 'What is VitePress?', link: '/guide/what-is-vitepress' }, - { text: 'Getting Started', link: '/guide/getting-started' }, - { text: 'Routing', link: '/guide/routing' }, - { text: 'Deploy', link: '/guide/deploy' } + { text: 'What is VitePress?', link: 'what-is-vitepress' }, + { text: 'Getting Started', link: 'getting-started' }, + { text: 'Routing', link: 'routing' }, + { text: 'Deploy', link: 'deploy' } ] }, { text: 'Writing', collapsed: false, items: [ - { text: 'Markdown Extensions', link: '/guide/markdown' }, - { text: 'Asset Handling', link: '/guide/asset-handling' }, - { text: 'Frontmatter', link: '/guide/frontmatter' }, - { text: 'Using Vue in Markdown', link: '/guide/using-vue' }, - { text: 'Internationalization', link: '/guide/i18n' } + { text: 'Markdown Extensions', link: 'markdown' }, + { text: 'Asset Handling', link: 'asset-handling' }, + { text: 'Frontmatter', link: 'frontmatter' }, + { text: 'Using Vue in Markdown', link: 'using-vue' }, + { text: 'Internationalization', link: 'i18n' } ] }, { text: 'Customization', collapsed: false, items: [ - { text: 'Using a Custom Theme', link: '/guide/custom-theme' }, - { - text: 'Extending the Default Theme', - link: '/guide/extending-default-theme' - }, - { text: 'Build-Time Data Loading', link: '/guide/data-loading' }, - { text: 'SSR Compatibility', link: '/guide/ssr-compat' }, - { text: 'Connecting to a CMS', link: '/guide/cms' } + { text: 'Using a Custom Theme', link: 'custom-theme' }, + { text: 'Extending the Default Theme', link: 'extending-default-theme' }, + { text: 'Build-Time Data Loading', link: 'data-loading' }, + { text: 'SSR Compatibility', link: 'ssr-compat' }, + { text: 'Connecting to a CMS', link: 'cms' } ] }, { text: 'Experimental', collapsed: false, items: [ - { - text: 'MPA Mode', - link: '/guide/mpa-mode' - }, - { - text: 'Sitemap Generation', - link: '/guide/sitemap-generation' - } + { text: 'MPA Mode', link: 'mpa-mode' }, + { text: 'Sitemap Generation', link: 'sitemap-generation' } ] }, - // { - // text: 'Migrations', - // collapsed: false, - // items: [ - // { - // text: 'Migration from VuePress', - // link: '/guide/migration-from-vuepress' - // }, - // { - // text: 'Migration from VitePress 0.x', - // link: '/guide/migration-from-vitepress-0' - // } - // ] - // }, - { - text: 'Config & API Reference', - link: '/reference/site-config' - } + { text: 'Config & API Reference', base: '/reference/', link: 'site-config' } ] } -function sidebarReference() { +/* prettier-ignore */ +function sidebarReference(): DefaultTheme.SidebarItem[] { return [ { text: 'Reference', items: [ - { text: 'Site Config', link: '/reference/site-config' }, - { text: 'Frontmatter Config', link: '/reference/frontmatter-config' }, - { text: 'Runtime API', link: '/reference/runtime-api' }, - { text: 'CLI', link: '/reference/cli' }, + { text: 'Site Config', link: 'site-config' }, + { text: 'Frontmatter Config', link: 'frontmatter-config' }, + { text: 'Runtime API', link: 'runtime-api' }, + { text: 'CLI', link: 'cli' }, { text: 'Default Theme', items: [ - { - text: 'Overview', - link: '/reference/default-theme-config' - }, - { - text: 'Nav', - link: '/reference/default-theme-nav' - }, - { - text: 'Sidebar', - link: '/reference/default-theme-sidebar' - }, - { - text: 'Home Page', - link: '/reference/default-theme-home-page' - }, - { - text: 'Footer', - link: '/reference/default-theme-footer' - }, - { - text: 'Layout', - link: '/reference/default-theme-layout' - }, - { - text: 'Badge', - link: '/reference/default-theme-badge' - }, - { - text: 'Team Page', - link: '/reference/default-theme-team-page' - }, - { - text: 'Prev / Next Links', - link: '/reference/default-theme-prev-next-links' - }, - { - text: 'Edit Link', - link: '/reference/default-theme-edit-link' - }, - { - text: 'Last Updated Timestamp', - link: '/reference/default-theme-last-updated' - }, - { - text: 'Search', - link: '/reference/default-theme-search' - }, - { - text: 'Carbon Ads', - link: '/reference/default-theme-carbon-ads' - } + { text: 'Overview', link: 'default-theme-config' }, + { text: 'Nav', link: 'default-theme-nav' }, + { text: 'Sidebar', link: 'default-theme-sidebar' }, + { text: 'Home Page', link: 'default-theme-home-page' }, + { text: 'Footer', link: 'default-theme-footer' }, + { text: 'Layout', link: 'default-theme-layout' }, + { text: 'Badge', link: 'default-theme-badge' }, + { text: 'Team Page', link: 'default-theme-team-page' }, + { text: 'Prev / Next Links', link: 'default-theme-prev-next-links' }, + { text: 'Edit Link', link: 'default-theme-edit-link' }, + { text: 'Last Updated Timestamp', link: 'default-theme-last-updated' }, + { text: 'Search', link: 'default-theme-search' }, + { text: 'Carbon Ads', link: 'default-theme-carbon-ads' } ] } ] diff --git a/src/client/theme-default/support/sidebar.ts b/src/client/theme-default/support/sidebar.ts index 5c2e646b..3f7d0b3e 100644 --- a/src/client/theme-default/support/sidebar.ts +++ b/src/client/theme-default/support/sidebar.ts @@ -7,6 +7,8 @@ export interface SidebarLink { link: string } +type SidebarItem = DefaultTheme.SidebarItem + /** * Get the `Sidebar` from sidebar option. This method will ensure to get correct * sidebar config from `MultiSideBarConfig` with various path combinations such @@ -14,20 +16,15 @@ export interface SidebarLink { * return empty array. */ export function getSidebar( - sidebar: DefaultTheme.Sidebar | undefined, + _sidebar: DefaultTheme.Sidebar | undefined, path: string -): DefaultTheme.SidebarItem[] { - if (Array.isArray(sidebar)) { - return sidebar - } - - if (sidebar == null) { - return [] - } +): SidebarItem[] { + if (Array.isArray(_sidebar)) return addBase(_sidebar) + if (_sidebar == null) return [] path = ensureStartingSlash(path) - const dir = Object.keys(sidebar) + const dir = Object.keys(_sidebar) .sort((a, b) => { return b.split('/').length - a.split('/').length }) @@ -36,16 +33,17 @@ export function getSidebar( return path.startsWith(ensureStartingSlash(dir)) }) - return dir ? sidebar[dir] : [] + const sidebar = dir ? _sidebar[dir] : [] + return Array.isArray(sidebar) + ? addBase(sidebar) + : addBase(sidebar.items, sidebar.base) } /** * Get or generate sidebar group from the given sidebar items. */ -export function getSidebarGroups( - sidebar: DefaultTheme.SidebarItem[] -): DefaultTheme.SidebarItem[] { - const groups: DefaultTheme.SidebarItem[] = [] +export function getSidebarGroups(sidebar: SidebarItem[]): SidebarItem[] { + const groups: SidebarItem[] = [] let lastGroupIndex: number = 0 @@ -67,12 +65,10 @@ export function getSidebarGroups( return groups } -export function getFlatSideBarLinks( - sidebar: DefaultTheme.SidebarItem[] -): SidebarLink[] { +export function getFlatSideBarLinks(sidebar: SidebarItem[]): SidebarLink[] { const links: SidebarLink[] = [] - function recursivelyExtractLinks(items: DefaultTheme.SidebarItem[]) { + function recursivelyExtractLinks(items: SidebarItem[]) { for (const item of items) { if (item.text && item.link) { links.push({ text: item.text, link: item.link }) @@ -94,7 +90,7 @@ export function getFlatSideBarLinks( */ export function hasActiveLink( path: string, - items: DefaultTheme.SidebarItem | DefaultTheme.SidebarItem[] + items: SidebarItem | SidebarItem[] ): boolean { if (Array.isArray(items)) { return items.some((item) => hasActiveLink(path, item)) @@ -106,3 +102,13 @@ export function hasActiveLink( ? hasActiveLink(path, items.items) : false } + +function addBase(items: SidebarItem[], _base?: string): SidebarItem[] { + return [...items].map((_item) => { + const item = { ..._item } + const base = item.base || _base + if (base && item.link) item.link = base + item.link + if (item.items) item.items = addBase(item.items, base) + return item + }) +} diff --git a/types/default-theme.d.ts b/types/default-theme.d.ts index 8e350a33..96b16c84 100644 --- a/types/default-theme.d.ts +++ b/types/default-theme.d.ts @@ -197,7 +197,7 @@ export namespace DefaultTheme { export type Sidebar = SidebarItem[] | SidebarMulti export interface SidebarMulti { - [path: string]: SidebarItem[] + [path: string]: SidebarItem[] | { items: SidebarItem[]; base: string } } export type SidebarItem = { @@ -224,6 +224,11 @@ export namespace DefaultTheme { * If `false`, group is collapsible but expanded by default */ collapsed?: boolean + + /** + * Base path for the children items. + */ + base?: string } /**