Add VitePress site with local login and deployment docs
Enable static site build on port 12100 with Express session auth, auto sidebar, and DEPLOY.md for Ubuntu/NPS setup. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
|
||||
const EXCLUDE_DIRS = new Set([
|
||||
'node_modules',
|
||||
'.vitepress',
|
||||
'server',
|
||||
'assets',
|
||||
'images',
|
||||
'.git',
|
||||
'金瓶梅',
|
||||
'黄帝内经',
|
||||
'健康学习到150岁 - 人体系统调优不完全指南',
|
||||
'梅花',
|
||||
])
|
||||
|
||||
type SidebarItem = {
|
||||
text: string
|
||||
link?: string
|
||||
collapsed?: boolean
|
||||
items?: SidebarItem[]
|
||||
}
|
||||
|
||||
function naturalCompare(a: string, b: string) {
|
||||
return a.localeCompare(b, 'zh-CN', { numeric: true, sensitivity: 'base' })
|
||||
}
|
||||
|
||||
function titleFromFilename(name: string) {
|
||||
const base = name.replace(/\.md$/i, '')
|
||||
if (/^readme$/i.test(base)) return '导读'
|
||||
return base
|
||||
}
|
||||
|
||||
function toLink(relativePath: string) {
|
||||
const normalized = relativePath.replace(/\\/g, '/').replace(/\.md$/i, '')
|
||||
if (normalized.endsWith('/README')) {
|
||||
return `/${normalized.slice(0, -'/README'.length)}/`
|
||||
}
|
||||
return `/${normalized}`
|
||||
}
|
||||
|
||||
function collectItems(dir: string, relativeDir: string): SidebarItem[] {
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
||||
const files = entries.filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
|
||||
const subdirs = entries.filter((entry) => entry.isDirectory())
|
||||
|
||||
const items: SidebarItem[] = files
|
||||
.sort((a, b) => naturalCompare(a.name, b.name))
|
||||
.map((file) => ({
|
||||
text: titleFromFilename(file.name),
|
||||
link: toLink(path.join(relativeDir, file.name)),
|
||||
}))
|
||||
|
||||
for (const subdir of subdirs.sort((a, b) => naturalCompare(a.name, b.name))) {
|
||||
const subRelative = path.join(relativeDir, subdir.name)
|
||||
const subItems = collectItems(path.join(dir, subdir.name), subRelative)
|
||||
if (subItems.length === 0) continue
|
||||
items.push({
|
||||
text: subdir.name,
|
||||
collapsed: true,
|
||||
items: subItems,
|
||||
})
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
export function generateSidebar(): Record<string, SidebarItem[]> {
|
||||
const groups: SidebarItem[] = []
|
||||
|
||||
const rootFiles = fs
|
||||
.readdirSync(root, { withFileTypes: true })
|
||||
.filter(
|
||||
(entry) =>
|
||||
entry.isFile() &&
|
||||
entry.name.endsWith('.md') &&
|
||||
entry.name !== 'README.md' &&
|
||||
entry.name !== 'index.md',
|
||||
)
|
||||
.sort((a, b) => naturalCompare(a.name, b.name))
|
||||
|
||||
if (rootFiles.length > 0) {
|
||||
groups.push({
|
||||
text: '典籍',
|
||||
collapsed: false,
|
||||
items: rootFiles.map((file) => ({
|
||||
text: titleFromFilename(file.name),
|
||||
link: toLink(file.name),
|
||||
})),
|
||||
})
|
||||
}
|
||||
|
||||
const dirs = fs
|
||||
.readdirSync(root, { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory() && !EXCLUDE_DIRS.has(entry.name))
|
||||
.sort((a, b) => naturalCompare(a.name, b.name))
|
||||
|
||||
for (const dir of dirs) {
|
||||
const items = collectItems(path.join(root, dir.name), dir.name)
|
||||
if (items.length === 0) continue
|
||||
groups.push({
|
||||
text: dir.name,
|
||||
collapsed: false,
|
||||
items,
|
||||
})
|
||||
}
|
||||
|
||||
return { '/': groups }
|
||||
}
|
||||
Reference in New Issue
Block a user