diff --git a/.gitignore b/.gitignore index 5553ef3..fc269e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ node_modules/ -.vitepress/cache/ \ No newline at end of file +.vitepress/cache/ + +.vitepress/dist/ \ No newline at end of file diff --git a/.vitepress/sidebar.mts b/.vitepress/sidebar.mts index 9dc824f..449f8ea 100644 --- a/.vitepress/sidebar.mts +++ b/.vitepress/sidebar.mts @@ -12,7 +12,9 @@ export type SidebarAutoItem = { export type SidebarItem = { text: string - link: string + link?: string + items?: SidebarItem[] + collapsed?: boolean } export type SidebarGroup = { @@ -20,6 +22,24 @@ export type SidebarGroup = { items: SidebarItem[] } +type OrderedLinkItem = { + order: number + text: string + link: string +} + +type NestedSection = { + order: number + text: string + items: SidebarItem[] + collapsed: boolean +} + +function sortByOrderAndText(a: T, b: T) { + if (a.order === b.order) return a.text.localeCompare(b.text) + return a.order - b.order +} + function normalizeLink(link: string) { if (!link.startsWith('/')) return `/${link}` return link @@ -65,35 +85,35 @@ function readFrontmatterAndTitle(filePath: string) { const raw = fs.readFileSync(filePath, 'utf8') const fmMatch = raw.match(/^---\s*([\s\S]*?)\s*---/) let order = Number.POSITIVE_INFINITY + let subheading: string | undefined if (fmMatch) { const fm = fmMatch[1] const orderMatch = fm.match(/^\s*order\s*:\s*([-+]?\d+(?:\.\d+)?)/m) if (orderMatch) order = Number(orderMatch[1]) + + const subheadingMatch = fm.match(/^\s*Subheading\s*:\s*(.+)$/m) + if (subheadingMatch) { + subheading = subheadingMatch[1].trim() + } } const h1 = raw.match(/^#\s+(.+)$/m) const title = h1 ? h1[1].trim() : path.basename(filePath, '.md') - return { order, title } + return { order, title, subheading } } -function generateSidebarGroup(entry: SidebarAutoItem): SidebarGroup { - const { absDir, isRoot } = resolvePathConfig(entry.path) - - if (!fs.existsSync(absDir)) { - return { text: entry.text, items: [] } - } - - const relativeDir = path.relative(docsRoot, absDir).split(path.sep).join('/') - const files = fs.readdirSync(absDir).filter((name) => { +function listMarkdownFiles(absDir: string, includeIndex: boolean) { + return fs.readdirSync(absDir).filter((name) => { if (!name.endsWith('.md')) return false - // When scanning docs root '/', do not include root index.md in sidebar. - if (isRoot && name.toLowerCase() === 'index.md') return false + if (!includeIndex && name.toLowerCase() === 'index.md') return false return true }) +} - const items = files +function listMarkdownItems(absDir: string, relativeDir: string, includeIndex: boolean) { + return listMarkdownFiles(absDir, includeIndex) .map((name) => { const full = path.join(absDir, name) const { order, title } = readFrontmatterAndTitle(full) @@ -109,14 +129,70 @@ function generateSidebarGroup(entry: SidebarAutoItem): SidebarGroup { link } }) - .sort((a, b) => { - if (a.order === b.order) return a.text.localeCompare(b.text) - return a.order - b.order - }) + .sort(sortByOrderAndText) +} + +function buildNestedSection(absDir: string, relativeDir: string, dirName: string): NestedSection | null { + const childAbsDir = path.join(absDir, dirName) + const childRelativeDir = relativeDir ? `${relativeDir}/${dirName}` : dirName + const indexPath = path.join(childAbsDir, 'index.md') + const sectionMeta = fs.existsSync(indexPath) + ? readFrontmatterAndTitle(indexPath) + : null + + if (!sectionMeta?.subheading) return null + + const sectionItems = listMarkdownItems(childAbsDir, childRelativeDir, true) + if (!sectionItems.length) return null + + return { + order: sectionMeta.order, + text: sectionMeta.subheading, + items: sectionItems.map(({ text, link }): SidebarItem => ({ text, link })), + collapsed: false + } +} + +function generateSidebarGroup(entry: SidebarAutoItem): SidebarGroup { + const { absDir, isRoot } = resolvePathConfig(entry.path) + + if (!fs.existsSync(absDir)) { + return { text: entry.text, items: [] } + } + + const relativeDir = path.relative(docsRoot, absDir).split(path.sep).join('/') + + const topLevelItems = listMarkdownItems( + absDir, + relativeDir, + // When scanning docs root '/', do not include root index.md in sidebar. + !isRoot + ) + + const nestedSections = fs.readdirSync(absDir, { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => buildNestedSection(absDir, relativeDir, dirent.name)) + .filter((item): item is NestedSection => Boolean(item)) + + const items = [ + ...topLevelItems.map((item) => ({ kind: 'link' as const, ...item })), + ...nestedSections.map((item) => ({ kind: 'section' as const, ...item })) + ] + .sort(sortByOrderAndText) return { text: entry.text, - items: items.map(({ text, link }): SidebarItem => ({ text, link })) + items: items.map((item): SidebarItem => { + if (item.kind === 'link') { + return { text: item.text, link: item.link } + } + + return { + text: item.text, + items: item.items, + collapsed: item.collapsed + } + }) } } diff --git a/kinesin/power.md b/kinesin/power.md index 1b6997c..abe010e 100644 --- a/kinesin/power.md +++ b/kinesin/power.md @@ -5,7 +5,7 @@ order: 4 # 权限获取 -根据插件权限机制,Kinesin需要获取以下权限: +Kinesin需要获取以下权限: ## 1. storage(存储)