[feat] Subheading
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
.vitepress/cache/
|
.vitepress/cache/
|
||||||
|
|
||||||
|
.vitepress/dist/
|
||||||
@@ -12,7 +12,9 @@ export type SidebarAutoItem = {
|
|||||||
|
|
||||||
export type SidebarItem = {
|
export type SidebarItem = {
|
||||||
text: string
|
text: string
|
||||||
link: string
|
link?: string
|
||||||
|
items?: SidebarItem[]
|
||||||
|
collapsed?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SidebarGroup = {
|
export type SidebarGroup = {
|
||||||
@@ -20,6 +22,24 @@ export type SidebarGroup = {
|
|||||||
items: SidebarItem[]
|
items: SidebarItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OrderedLinkItem = {
|
||||||
|
order: number
|
||||||
|
text: string
|
||||||
|
link: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type NestedSection = {
|
||||||
|
order: number
|
||||||
|
text: string
|
||||||
|
items: SidebarItem[]
|
||||||
|
collapsed: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortByOrderAndText<T extends { order: number; text: string }>(a: T, b: T) {
|
||||||
|
if (a.order === b.order) return a.text.localeCompare(b.text)
|
||||||
|
return a.order - b.order
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeLink(link: string) {
|
function normalizeLink(link: string) {
|
||||||
if (!link.startsWith('/')) return `/${link}`
|
if (!link.startsWith('/')) return `/${link}`
|
||||||
return link
|
return link
|
||||||
@@ -65,35 +85,35 @@ function readFrontmatterAndTitle(filePath: string) {
|
|||||||
const raw = fs.readFileSync(filePath, 'utf8')
|
const raw = fs.readFileSync(filePath, 'utf8')
|
||||||
const fmMatch = raw.match(/^---\s*([\s\S]*?)\s*---/)
|
const fmMatch = raw.match(/^---\s*([\s\S]*?)\s*---/)
|
||||||
let order = Number.POSITIVE_INFINITY
|
let order = Number.POSITIVE_INFINITY
|
||||||
|
let subheading: string | undefined
|
||||||
|
|
||||||
if (fmMatch) {
|
if (fmMatch) {
|
||||||
const fm = fmMatch[1]
|
const fm = fmMatch[1]
|
||||||
const orderMatch = fm.match(/^\s*order\s*:\s*([-+]?\d+(?:\.\d+)?)/m)
|
const orderMatch = fm.match(/^\s*order\s*:\s*([-+]?\d+(?:\.\d+)?)/m)
|
||||||
if (orderMatch) order = Number(orderMatch[1])
|
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 h1 = raw.match(/^#\s+(.+)$/m)
|
||||||
const title = h1 ? h1[1].trim() : path.basename(filePath, '.md')
|
const title = h1 ? h1[1].trim() : path.basename(filePath, '.md')
|
||||||
|
|
||||||
return { order, title }
|
return { order, title, subheading }
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateSidebarGroup(entry: SidebarAutoItem): SidebarGroup {
|
function listMarkdownFiles(absDir: string, includeIndex: boolean) {
|
||||||
const { absDir, isRoot } = resolvePathConfig(entry.path)
|
return fs.readdirSync(absDir).filter((name) => {
|
||||||
|
|
||||||
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) => {
|
|
||||||
if (!name.endsWith('.md')) return false
|
if (!name.endsWith('.md')) return false
|
||||||
// When scanning docs root '/', do not include root index.md in sidebar.
|
if (!includeIndex && name.toLowerCase() === 'index.md') return false
|
||||||
if (isRoot && name.toLowerCase() === 'index.md') return false
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const items = files
|
function listMarkdownItems(absDir: string, relativeDir: string, includeIndex: boolean) {
|
||||||
|
return listMarkdownFiles(absDir, includeIndex)
|
||||||
.map((name) => {
|
.map((name) => {
|
||||||
const full = path.join(absDir, name)
|
const full = path.join(absDir, name)
|
||||||
const { order, title } = readFrontmatterAndTitle(full)
|
const { order, title } = readFrontmatterAndTitle(full)
|
||||||
@@ -109,14 +129,70 @@ function generateSidebarGroup(entry: SidebarAutoItem): SidebarGroup {
|
|||||||
link
|
link
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.sort((a, b) => {
|
.sort(sortByOrderAndText)
|
||||||
if (a.order === b.order) return a.text.localeCompare(b.text)
|
}
|
||||||
return a.order - b.order
|
|
||||||
})
|
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 {
|
return {
|
||||||
text: entry.text,
|
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
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ order: 4
|
|||||||
|
|
||||||
# 权限获取
|
# 权限获取
|
||||||
|
|
||||||
根据插件权限机制,Kinesin需要获取以下权限:
|
Kinesin需要获取以下权限:
|
||||||
|
|
||||||
## 1. storage(存储)
|
## 1. storage(存储)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user