diff --git a/e2e/fixtures/auto-nav-sidebar/doc/api/_meta.json b/e2e/fixtures/auto-nav-sidebar/doc/api/_meta.json index 8ae5a14a0..2908df3e9 100644 --- a/e2e/fixtures/auto-nav-sidebar/doc/api/_meta.json +++ b/e2e/fixtures/auto-nav-sidebar/doc/api/_meta.json @@ -29,5 +29,14 @@ "label": "Link" }, "commands", - "single-page" + "single-page", + { + "type": "section-header", + "label": "Section Header" + }, + { + "type": "dir-section-header", + "name": "section-header", + "label": "Dir Section Header" + } ] diff --git a/e2e/fixtures/auto-nav-sidebar/doc/api/section-header/_meta.json b/e2e/fixtures/auto-nav-sidebar/doc/api/section-header/_meta.json new file mode 100644 index 000000000..84ce07d3b --- /dev/null +++ b/e2e/fixtures/auto-nav-sidebar/doc/api/section-header/_meta.json @@ -0,0 +1 @@ +["section-a", "section-b"] diff --git a/e2e/fixtures/auto-nav-sidebar/doc/api/section-header/section-a.mdx b/e2e/fixtures/auto-nav-sidebar/doc/api/section-header/section-a.mdx new file mode 100644 index 000000000..6818b3666 --- /dev/null +++ b/e2e/fixtures/auto-nav-sidebar/doc/api/section-header/section-a.mdx @@ -0,0 +1 @@ +# Section a diff --git a/e2e/fixtures/auto-nav-sidebar/doc/api/section-header/section-b.mdx b/e2e/fixtures/auto-nav-sidebar/doc/api/section-header/section-b.mdx new file mode 100644 index 000000000..0044e54f6 --- /dev/null +++ b/e2e/fixtures/auto-nav-sidebar/doc/api/section-header/section-b.mdx @@ -0,0 +1 @@ +# Section b diff --git a/e2e/fixtures/auto-nav-sidebar/index.test.ts b/e2e/fixtures/auto-nav-sidebar/index.test.ts index 6acd72433..421eb004b 100644 --- a/e2e/fixtures/auto-nav-sidebar/index.test.ts +++ b/e2e/fixtures/auto-nav-sidebar/index.test.ts @@ -37,7 +37,14 @@ 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', 'Client API', 'Commands', 'Single'].join(','), + [ + 'Config', + 'Client API', + 'Commands', + 'Single', + 'Section a', + 'Section b', + ].join(','), ); const h3 = await page.$$('.overview-group_f8331 h3'); @@ -54,6 +61,8 @@ test.describe('Auto nav and sidebar test', async () => { 'Components', 'Commands', 'Single', + 'Section a', + 'Section b', ].join(','), ); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 9407b4a00..0c214eafd 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -30,12 +30,18 @@ export { } from '@rspress/shared/node-utils'; // cli export { defineConfig } from './cli/config'; -export { - build, - dev, - remarkFileCodeBlock, - remarkLink, - serve, -} from './node'; +export { build, dev, remarkFileCodeBlock, remarkLink, serve } from './node'; +export type { + CustomLinkMeta, + DirSectionHeaderSideMeta, + DirSideMeta, + DividerSideMeta, + FileSideMeta, + MetaJson, + NavJson, + SectionHeaderMeta, + SideMeta, + SideMetaItem, +} from './node/auto-nav-sidebar/type'; export { PluginDriver } from './node/PluginDriver'; export { RouteService } from './node/route/RouteService'; diff --git a/packages/core/src/node/auto-nav-sidebar/normalize.ts b/packages/core/src/node/auto-nav-sidebar/normalize.ts index aeafff648..07a4f9968 100644 --- a/packages/core/src/node/auto-nav-sidebar/normalize.ts +++ b/packages/core/src/node/auto-nav-sidebar/normalize.ts @@ -12,6 +12,8 @@ import { logger } from '@rspress/shared/logger'; import { absolutePathToRoutePath, addRoutePrefix } from '../route/RoutePage'; import type { CustomLinkMeta, + DirSectionHeaderSideMeta, + DirSideMeta, DividerSideMeta, FileSideMeta, SectionHeaderMeta, @@ -23,7 +25,7 @@ import { readJson, } from './utils'; -function getHmrFileKey(realPath: string | undefined, docsDir: string) { +function getFileKey(realPath: string | undefined, docsDir: string) { return realPath ? slash(relative(docsDir, realPath).replace(extname(realPath), '')) : ''; @@ -84,8 +86,10 @@ async function metaItemToSidebarItem( extensions: string[], metaFileSet: Set, mdFileSet: Set, - isFirstDir: boolean = false, // TODO: removed in the future, single-page mode -): Promise { +): Promise< + | (SidebarItem | SidebarGroup | SidebarDivider | SidebarSectionHeader) + | (SidebarItem | SidebarGroup | SidebarDivider | SidebarSectionHeader)[] +> { if (typeof metaItem === 'string') { return metaFileItemToSidebarItem( metaItem, @@ -108,141 +112,37 @@ async function metaItemToSidebarItem( } if (type === 'dir') { - const { - name, - label, - collapsible, - collapsed, - tag: metaJsonTag, - context: metaJsonContext, - overviewHeaders: metaJsonOverviewHeaders, - } = metaItem; - const dirAbsolutePath = join(workDir, name); - const dirMetaJsonPath = join(dirAbsolutePath, '_meta.json'); - const isMetaJsonExist = await pathExists(dirMetaJsonPath); - - let dirMetaJson: SideMetaItem[] = []; - if (isMetaJsonExist) { - metaFileSet.add(dirMetaJsonPath); - dirMetaJson = await readJson(dirMetaJsonPath); - } else { - dirMetaJson = await fsDirToMetaItems(dirAbsolutePath, extensions); - } - - async function getItems(withoutIndex: boolean = false) { - const items = await Promise.all( - (withoutIndex - ? dirMetaJson.filter(i => { - let name: string; - if (typeof i === 'object' && 'type' in i && i.type === 'file') { - name = i.name; - } else if (typeof i === 'string') { - name = i; - } else { - return true; - } - return name !== 'index.md' && i !== 'index.mdx' && i !== 'index'; - }) - : dirMetaJson - ).map(item => - metaItemToSidebarItem( - item, - dirAbsolutePath, - docsDir, - extensions, - metaFileSet, - mdFileSet, - ), - ), - ); - return items; - } - - try { - // Category index convention, display a document when clicking on the sidebar directory - // https://docusaurus.io/docs/sidebar/autogenerated#category-index-convention - - // 1. sameName /api folder, /api.md or /api.mdx, - const sameNameFile = await metaFileItemToSidebarItem( - name, - workDir, - docsDir, - extensions, - mdFileSet, - ); + return metaDirItemToSidebarItem( + metaItem, + workDir, + docsDir, + extensions, + metaFileSet, + mdFileSet, + false, + ); + } - const { link, text, _fileKey, context, overviewHeaders, tag } = - sameNameFile; - return { - text: label || text || name, - collapsible, - collapsed, - items: await getItems(), - link, - tag: metaJsonTag || tag, - overviewHeaders: metaJsonOverviewHeaders || overviewHeaders, - context: metaJsonContext || context, - _fileKey, - } satisfies SidebarGroup; - } catch (e) { - logger.debug(e); - const isIndexInMetaJsonIndex = dirMetaJson.find(i => { - if (typeof i === 'string') { - return i.replace(extname(i), '') === 'index'; - } - return false; - }); - - // 2. if "index.mdx" or "index.md" or "index" is in _meta.json || "index" file not exists, index page should be placed to child sidebar, the directory itself is not clickable - const isIndexFileExists = - (await pathExists(join(dirAbsolutePath, 'index.mdx'))) || - (await pathExists(join(dirAbsolutePath, 'index.md'))); - - if ((isMetaJsonExist && isIndexInMetaJsonIndex) || !isIndexFileExists) { - return { - text: label || name, - collapsible, - collapsed, - items: await getItems(), - link: undefined, - tag: metaJsonTag, - overviewHeaders: metaJsonOverviewHeaders, - context: metaJsonContext, - _fileKey: getHmrFileKey(dirAbsolutePath, docsDir), - } satisfies SidebarGroup; - } else { - // 3. if not, index page should be placed to child sidebar, the directory itself is not clickable - const indexFile = await metaFileItemToSidebarItem( - 'index', - dirAbsolutePath, - docsDir, - extensions, - mdFileSet, - ); - - const { link, text, _fileKey, context, overviewHeaders, tag } = - indexFile; - return { - text: label || text || name, - collapsible, - collapsed, - items: await getItems(!isFirstDir), - link, - tag: metaJsonTag || tag, - overviewHeaders: metaJsonOverviewHeaders || overviewHeaders, - context: metaJsonContext || context, - _fileKey, - } satisfies SidebarGroup; - } - } + if (type === 'dir-section-header') { + return metaDirSectionHeaderItemToSidebarItem( + metaItem, + workDir, + docsDir, + extensions, + metaFileSet, + mdFileSet, + ); } if (type === 'custom-link') { return metaCustomLinkItemToSidebarItem(metaItem, workDir, docsDir); } - if (type === 'divider' || type === 'section-header') { + if (type === 'divider') { return metaDividerToSidebarItem(metaItem); } + if (type === 'section-header') { + return metaSectionHeaderToSidebarItem(metaItem); + } throw new Error( `Unknown meta item type: ${(metaItem as any).type}, please check it in "${join(workDir, '_meta.json')}".`, @@ -316,10 +216,189 @@ async function metaFileItemToSidebarItem( tag, overviewHeaders: info.overviewHeaders || overviewHeaders, context: info.context || context, - _fileKey: getHmrFileKey(absolutePathWithExt, docsDir), + _fileKey: getFileKey(absolutePathWithExt, docsDir), } satisfies SidebarItem; } +async function metaDirItemToSidebarItem( + metaItem: DirSideMeta, + workDir: string, + docsDir: string, + extensions: string[], + metaFileSet: Set, + mdFileSet: Set, + forceIndexFileAsItem: boolean, +): Promise { + const { + name, + label, + collapsible, + collapsed, + tag: metaJsonTag, + context: metaJsonContext, + overviewHeaders: metaJsonOverviewHeaders, + } = metaItem; + const dirAbsolutePath = join(workDir, name); + const dirMetaJsonPath = join(dirAbsolutePath, '_meta.json'); + const isMetaJsonExist = await pathExists(dirMetaJsonPath); + + let dirMetaJson: SideMetaItem[] = []; + if (isMetaJsonExist) { + metaFileSet.add(dirMetaJsonPath); + dirMetaJson = await readJson(dirMetaJsonPath); + } else { + dirMetaJson = await fsDirToMetaItems(dirAbsolutePath, extensions); + } + + async function getItems( + withoutIndex: boolean = false, + ): Promise< + (SidebarItem | SidebarGroup | SidebarDivider | SidebarSectionHeader)[] + > { + const items = await Promise.all( + (withoutIndex + ? dirMetaJson.filter(i => { + let name: string; + if (typeof i === 'object' && 'type' in i && i.type === 'file') { + name = i.name; + } else if (typeof i === 'string') { + name = i; + } else { + return true; + } + return name !== 'index.md' && i !== 'index.mdx' && i !== 'index'; + }) + : dirMetaJson + ).map(item => + metaItemToSidebarItem( + item, + dirAbsolutePath, + docsDir, + extensions, + metaFileSet, + mdFileSet, + ), + ), + ); + return items.flat(); + } + + try { + // Category index convention, display a document when clicking on the sidebar directory + // https://docusaurus.io/docs/sidebar/autogenerated#category-index-convention + + // 1. sameName /api folder, /api.md or /api.mdx, + const sameNameFile = await metaFileItemToSidebarItem( + name, + workDir, + docsDir, + extensions, + mdFileSet, + ); + + const { link, text, _fileKey, context, overviewHeaders, tag } = + sameNameFile; + return { + text: label || text || name, + collapsible, + collapsed, + items: await getItems(), + link, + tag: metaJsonTag || tag, + overviewHeaders: metaJsonOverviewHeaders || overviewHeaders, + context: metaJsonContext || context, + _fileKey, + } satisfies SidebarGroup; + } catch (e) { + logger.debug(e); + const isIndexInMetaJsonIndex = dirMetaJson.find(i => { + if (typeof i === 'string') { + return i.replace(extname(i), '') === 'index'; + } + return false; + }); + + // 2. if "index.mdx" or "index.md" or "index" is in _meta.json || "index" file not exists, index page should be placed to child sidebar, the directory itself is not clickable + const isIndexFileExists = + (await pathExists(join(dirAbsolutePath, 'index.mdx'))) || + (await pathExists(join(dirAbsolutePath, 'index.md'))); + + if ((isMetaJsonExist && isIndexInMetaJsonIndex) || !isIndexFileExists) { + return { + text: label || name, + collapsible, + collapsed, + items: await getItems(), + overviewHeaders: metaJsonOverviewHeaders, + context: metaJsonContext, + _fileKey: getFileKey(dirAbsolutePath, docsDir), + } satisfies SidebarGroup; + } else { + // 3. if not, index page should be placed to child sidebar, the directory itself is not clickable + const indexFile = await metaFileItemToSidebarItem( + 'index', + dirAbsolutePath, + docsDir, + extensions, + mdFileSet, + ); + + const { link, text, _fileKey, context, overviewHeaders, tag } = indexFile; + return { + text: label || text || name, + collapsible, + collapsed, + items: await getItems(!forceIndexFileAsItem), + link, + tag: metaJsonTag || tag, + overviewHeaders: metaJsonOverviewHeaders || overviewHeaders, + context: metaJsonContext || context, + _fileKey, + } satisfies SidebarGroup; + } + } +} + +async function metaDirSectionHeaderItemToSidebarItem( + metaItem: DirSectionHeaderSideMeta, + workDir: string, + docsDir: string, + extensions: string[], + metaFileSet: Set, + mdFileSet: Set, +): Promise< + (SidebarItem | SidebarSectionHeader | SidebarGroup | SidebarDivider)[] +> { + const fakeDirMetaItem: DirSideMeta = { + ...metaItem, + type: 'dir', + }; + + const fakeSectionHeaderMetaItem: SectionHeaderMeta = { + label: metaItem.label, + tag: metaItem.tag, + type: 'section-header', + }; + + const dirSideGroup = await metaDirItemToSidebarItem( + fakeDirMetaItem, + workDir, + docsDir, + extensions, + metaFileSet, + mdFileSet, + true, + ); + const sectionHeaderSideItem = metaSectionHeaderToSidebarItem( + fakeSectionHeaderMetaItem, + ); + + return [ + sectionHeaderSideItem, + ...(dirSideGroup.items.length >= 1 ? dirSideGroup.items : []), + ]; +} + function metaCustomLinkItemToSidebarItem( metaItem: CustomLinkMeta, workDir: string, @@ -383,17 +462,16 @@ function metaCustomLinkItemToSidebarItem( ); } -function metaDividerToSidebarItem( - metaItem: DividerSideMeta | SectionHeaderMeta, -): SidebarDivider | SidebarSectionHeader { - const { type } = metaItem; - if (type === 'divider') { - const { dashed } = metaItem; - return { - dividerType: dashed ? 'dashed' : 'solid', - } satisfies SidebarDivider; - } +function metaDividerToSidebarItem(metaItem: DividerSideMeta) { + const { dashed } = metaItem; + return { + dividerType: dashed ? 'dashed' : 'solid', + } satisfies SidebarDivider; +} +function metaSectionHeaderToSidebarItem( + metaItem: SectionHeaderMeta, +): SidebarSectionHeader { // section header const { label, tag } = metaItem; return { @@ -409,7 +487,7 @@ async function scanSideMeta( metaFileSet: Set, mdFileSet: Set, ) { - const dir = (await metaItemToSidebarItem( + const dir = await metaDirItemToSidebarItem( { type: 'dir', name: '', @@ -419,8 +497,8 @@ async function scanSideMeta( extensions, metaFileSet, mdFileSet, - true, - )) as SidebarGroup; + true, // first dir + ); return dir.items; } diff --git a/packages/core/src/node/auto-nav-sidebar/type.ts b/packages/core/src/node/auto-nav-sidebar/type.ts index 1bbb2b747..8cb8be40b 100644 --- a/packages/core/src/node/auto-nav-sidebar/type.ts +++ b/packages/core/src/node/auto-nav-sidebar/type.ts @@ -21,6 +21,9 @@ export type DirSideMeta = { collapsed?: boolean; }; +export type DirSectionHeaderSideMeta = Omit & + Omit & { type: 'dir-section-header' }; + export type DividerSideMeta = { type: 'divider'; dashed?: boolean; @@ -84,6 +87,7 @@ export type CustomLinkMeta = export type SideMetaItem = | FileSideMeta | DirSideMeta + | DirSectionHeaderSideMeta | DividerSideMeta | SectionHeaderMeta | CustomLinkMeta diff --git a/website/docs/en/guide/basic/auto-nav-sidebar.mdx b/website/docs/en/guide/basic/auto-nav-sidebar.mdx index 9a8ae6d3e..e933f9e08 100644 --- a/website/docs/en/guide/basic/auto-nav-sidebar.mdx +++ b/website/docs/en/guide/basic/auto-nav-sidebar.mdx @@ -47,7 +47,7 @@ docs ## JSON schema type hint -To better edit `_nav.json` and `_meta.json` files, Rspress V2 provides two schema files `@rspress/core/meta-json-schema.json` and `@rspress/core/nav-json-schema.json` for ide type hinting. +To better edit `_nav.json` and `_meta.json` files, Rspress V2 provides two schema files `@rspress/core/meta-json-schema.json` and `@rspress/core/nav-json-schema.json` for IDE type hinting. For example, in VSCode, you can add the following configuration in `.vscode/settings.json`: @@ -63,15 +63,16 @@ For example, in VSCode, you can add the following configuration in `.vscode/sett { "fileMatch": ["**/_nav.json"], "url": "./node_modules/@rspress/core/nav-json-schema.json" - // or "url": "https://unpkg.com/@rspress/core@2.0.0-beta.22/nav-json-schema.json" + // or "url": "https://unpkg.com/@rspress/core@2.0.0-beta.21/nav-json-schema.json" } ] + // ... } ``` ## Navbar level config -In the case of the navigation bar level, you can fill in an array in `_nav.json`, and its type is exactly the same as the nav config of the default theme. For details, please refer to [nav config](/api/config/config-theme.html#nav). for example: +At the navigation bar level, you can fill in an array in `_nav.json`, and its type is exactly the same as the nav config of the default theme. For details, please refer to [nav config](/api/config/config-theme.html#nav). For example: ```json title="docs/_nav.json" [ @@ -85,7 +86,7 @@ In the case of the navigation bar level, you can fill in an array in `_nav.json` ## Sidebar level config -In the case of the sidebar level, you can fill in `_meta.json` an array with each item of the following type: +At the sidebar level, you can fill in an array in `_meta.json`, with each item of the following type: ```ts export type FileSideMeta = { @@ -108,6 +109,9 @@ export type DirSideMeta = { context?: string; }; +export type DirSectionHeaderSideMeta = Omit & + Omit & { type: 'dir-section-header' }; + export type DividerSideMeta = { type: 'divider'; dashed?: boolean; @@ -145,6 +149,7 @@ export type CustomLinkMeta = export type SideMetaItem = | FileSideMeta | DirSideMeta + | DirSectionHeaderSideMeta | DividerSideMeta | SectionHeaderMeta | CustomLinkMeta @@ -176,7 +181,7 @@ export type FileSideMeta = { }; ``` -Among them, `name` means the file name, `with`/`without` suffix is ​​supported, `label` means the display name of the file in the sidebar.`label` is an optional value, if it is not filled, it will automatically take the h1 title in the document. `overviewHeaders` means the headers displayed in the overview page of the file. It is an optional value and the default value is `[2]`. `context` means adding the value of the `data-context` attribute to the DOM node when generating the sidebar, it is an optional value and will not be added by default. For example: +Here, `name` means the file name, with or without a suffix is supported, `label` means the display name of the file in the sidebar. `label` is optional, if not filled, it will automatically take the h1 title in the document. `overviewHeaders` means the headers displayed in the overview page of the file. It is optional and the default value is `[2]`. `context` means adding the value of the `data-context` attribute to the DOM node when generating the sidebar, it is optional and will not be added by default. For example: ```json { @@ -203,7 +208,7 @@ export type DirSideMeta = { }; ``` -Among them, `name` indicates the directory name, `label` indicates the display name of the directory in the sidebar, `collapsible` indicates whether the directory can be collapsed, `collapsed` indicates whether the directory is collapsed by default, and `overviewHeaders` indicates the headers displayed on the overview page for files in this directory. It is an optional value and the default value is `[2]`. `context` means adding the value of the `data-context` attribute to the DOM node when generating the sidebar, it is an optional value and will not be added by default. For example: +Here, `name` indicates the directory name, `label` indicates the display name of the directory in the sidebar, `collapsible` indicates whether the directory can be collapsed, `collapsed` indicates whether the directory is collapsed by default, and `overviewHeaders` indicates the headers displayed on the overview page for files in this directory. It is optional and the default value is `[2]`. `context` means adding the value of the `data-context` attribute to the DOM node when generating the sidebar, it is optional and will not be added by default. For example: ```json { @@ -215,6 +220,29 @@ Among them, `name` indicates the directory name, `label` indicates the display n } ``` +### dir-section-header + +When describing a **directory**, you can also use `dir-section-header`, which only differs from `"type": "dir"` in UI. It is often used at the first level, where the directory title is displayed as a [section header](#section-header) and is at the same level as files under the directory. + +Type: + +```ts +export type DirSectionHeaderSideMeta = Omit & + Omit & { type: 'dir-section-header' }; +``` + +```json +{ + "type": "dir-section-header", + "name": "advanced", + "label": "Advanced", + "collapsible": true, + "collapsed": false +} +``` + +### divider + In the case of describing **divider**, the types are as follows: ```ts @@ -312,7 +340,7 @@ export type CustomLinkMeta = }; ``` -Among them, `link` indicates the link address, `label` indicates the display name of the link in the sidebar, for example: +Here, `link` indicates the link address, `label` indicates the display name of the link in the sidebar, for example: ```json { @@ -322,7 +350,7 @@ Among them, `link` indicates the link address, `label` indicates the display nam } ``` -`link` support external links, for example: +`link` supports external links, for example: ```json { @@ -404,7 +432,7 @@ In the guides directory you can configure `_meta.json` as follows: ] ``` -In `basic` directory, you may not configure `_meta.json`, and then Rspress will automatically generate a sidebar for you, the default is sorted alphabetically according to the file name. If you want to customize the order, you can prefix the file name with a number, such as: +In the `basic` directory, you may not configure `_meta.json`, and then Rspress will automatically generate a sidebar for you, the default is sorted alphabetically according to the file name. If you want to customize the order, you can prefix the file name with a number, such as: ```txt basic diff --git a/website/docs/zh/guide/basic/auto-nav-sidebar.mdx b/website/docs/zh/guide/basic/auto-nav-sidebar.mdx index 114fca017..5bfdfde04 100644 --- a/website/docs/zh/guide/basic/auto-nav-sidebar.mdx +++ b/website/docs/zh/guide/basic/auto-nav-sidebar.mdx @@ -109,6 +109,9 @@ export type DirSideMeta = { context?: string; }; +export type DirSectionHeaderSideMeta = Omit & + Omit & { type: 'dir-section-header' }; + export type DividerSideMeta = { type: 'divider'; dashed?: boolean; @@ -146,6 +149,7 @@ export type CustomLinkMeta = export type SideMetaItem = | FileSideMeta | DirSideMeta + | DirSectionHeaderSideMeta | DividerSideMeta | SectionHeaderMeta | CustomLinkMeta @@ -204,7 +208,7 @@ export type DirSideMeta = { }; ``` -其中,`name` 表示目录名,`label` 表示该目录在侧边栏中的显示名称,`collapsible` 表示该目录是否可以折叠,`collapsed` 表示该目录是否默认折叠,`overviewHeaders` 表示该目录下的文件在预览页中展示的标题级别,为可选值,默认为 `[2]`。`context` 表示在生成侧边栏时在所在的 DOM 节点添加 `data-context` 属性的值。为可选值,默认不会添加。比如: +其中,`name` 表示目录名,`label` 表示该目录在侧边栏中的显示名称,`collapsible` 表示该目录是否可以折叠,`collapsed` 表示该目录是否默认折叠,`overviewHeaders` 表示该目录下的文件在预览页中展示的标题级别,为可选值,默认为 `[2]`,即 h2。`context` 表示在生成侧边栏时在所在的 DOM 节点添加 `data-context` 属性的值。为可选值,默认不会添加。比如: ```json { @@ -216,6 +220,27 @@ export type DirSideMeta = { } ``` +### dir-section-header + +当描述**目录**时,你也可以使用 `dir-section-header`,它与 `"type": "dir"` 仅在 UI 上有所变化,常用于第一级别,目录标题会以 [分组标题](#section-header) 的形式展示,并与目录下的文件处于同一层级。 + +类型为: + +```ts +export type DirSectionHeaderSideMeta = Omit & + Omit & { type: 'dir-section-header' }; +``` + +```json +{ + "type": "dir-section-header", + "name": "advanced", + "label": "Advanced", + "collapsible": true, + "collapsed": false +} +``` + ### divider 在描述**分割线**的情况下,类型如下: