feat: use VitePress to serve documents (#14)

* init vitepress

* fix build

* add vue
This commit is contained in:
clas 2025-01-25 22:02:59 +08:00 committed by GitHub
parent f757775c10
commit 27684e03ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 2092 additions and 1 deletions

2
.gitignore vendored
View file

@ -1 +1,3 @@
.DS_Store
node_modules
.vitepress/cache

28
.vitepress/config.mts Normal file
View file

@ -0,0 +1,28 @@
import { defineConfig } from 'vitepress'
import { getMeetingMinutesSidebar } from '../meetings/sidebar'
// https://vitepress.dev/reference/site-config
export default defineConfig({
title: "Documents",
description: "Documents for nbtca",
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: 'Home', link: '/' },
{ text: 'Meeting Minutes', link: '/meetings' }
],
sidebar: [
{
text: 'Meeting Minutes',
link: '/meetings'
},
...getMeetingMinutesSidebar()
],
socialLinks: [
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' }
]
}
})

6
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,6 @@
{
"vue.server.includeLanguages": [
"vue",
"markdown"
]
}

25
index.md Normal file
View file

@ -0,0 +1,25 @@
---
# https://vitepress.dev/reference/default-theme-home-page
layout: home
hero:
name: "Documents"
text: "Documents for nbtca"
tagline: My great project tagline
actions:
- theme: brand
text: Meeting minutes
link: /meetings/index
# - theme: alt
# text: API Examples
# link: /api-examples
features:
- title: Feature A
details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
- title: Feature B
details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
- title: Feature C
details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
---

1
meetings/index.md Normal file
View file

@ -0,0 +1 @@
index

65
meetings/sidebar.ts Normal file
View file

@ -0,0 +1,65 @@
import { scanDir } from "../utils/sidebar"
import dayjs from "dayjs"
type MeetingMinutesParsed = {
date: Date
fileNameWithoutDate: string
fileName: string
link: string
}
export const stripFileExtension = (filename: string) => {
return filename.split(".").slice(0, -1).join(".")
}
const parseFileName = (fileName: string, link: string): MeetingMinutesParsed => {
const dateString = fileName.slice(0, 10)
const date = new Date(dateString)
return {
date,
fileNameWithoutDate: stripFileExtension(fileName.slice(10)),
fileName: fileName,
link: link
}
}
export const getMeetingMinutesSidebar = () => {
const items = scanDir("meetings").filter(v => {
return v.filename != "index.md"
})
const groupedItems = items.reduce((acc, item) => {
const parsed = parseFileName(item.filename, item.link)
const year = parsed.date.getFullYear()
if (!acc[year]) {
acc[year] = []
}
acc[year].push(parsed)
return acc
}, {} as Record<number, MeetingMinutesParsed[]>)
const groupedSidebarItems = Object.keys(groupedItems).map(v => {
const items: MeetingMinutesParsed[] = groupedItems[v]
return {
text: v.toString(),
items: items
.sort((a, b) => {
return a.date > b.date ? 1 : -1
})
.map(v => {
return {
text: `${dayjs(v.date).format("YYYY-MM-DD")} ${v.fileNameWithoutDate}`,
link: v.link
}
})
}
})
return groupedSidebarItems.sort((a, b) => {
return b.text > a.text ? 1 : -1
})
}

24
package.json Normal file
View file

@ -0,0 +1,24 @@
{
"name": "documents",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "vitest",
"docs:dev": "vitepress dev",
"docs:build": "vitepress build",
"docs:preview": "vitepress preview"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"vitepress": "^1.6.3",
"vitest": "^3.0.4"
},
"dependencies": {
"@types/node": "^22.10.10",
"dayjs": "^1.11.13",
"vue": "^3.5.13"
}
}

1869
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

15
tsconfig.json Normal file
View file

@ -0,0 +1,15 @@
{
"include": [
"**/*.ts",
"**/*.vue",
"**/*.md",
],
"compilerOptions": {
"esModuleInterop": true
},
"vueCompilerOptions": {
"vitePressExtensions": [
".md"
],
},
}

40
utils/sidebar.test.ts Normal file
View file

@ -0,0 +1,40 @@
import { describe, expect, test, beforeAll, afterAll } from "vitest";
import { scanDir } from "../utils/sidebar";
import { resolve } from "path";
import { mkdtempSync, writeFileSync, rmdirSync } from "fs";
import { tmpdir } from "os";
import { join } from "path";
let tempDir: string;
beforeAll(() => {
tempDir = mkdtempSync(join(tmpdir(), 'test-'));
writeFileSync(join(tempDir, 'test1.md'), '# Test 1');
writeFileSync(join(tempDir, 'test2.md'), '# Test 2');
writeFileSync(join(tempDir, 'test.txt'), 'Test text file');
});
afterAll(() => {
rmdirSync(tempDir, { recursive: true });
});
describe("scanDir", () => {
test("should return markdown files with correct structure", () => {
const res = scanDir(tempDir);
expect(res).toBeInstanceOf(Array);
expect(res.length).toBe(2);
res.forEach(item => {
expect(item).toHaveProperty("filename");
expect(item).toHaveProperty("link");
expect(item.filename).toMatch(/\.md$/);
expect(item.link).toBe(resolve(tempDir, item.filename));
});
});
test("should return an empty array if no markdown files are found", () => {
const emptyDir = mkdtempSync(join(tmpdir(), 'empty-'));
const res = scanDir(emptyDir);
expect(res).toEqual([]);
rmdirSync(emptyDir, { recursive: true });
});
});

16
utils/sidebar.ts Normal file
View file

@ -0,0 +1,16 @@
import { readdirSync } from "fs"
import path from "path"
export const scanDir = (dirname: string) => {
const dirpath = path.resolve(__dirname, `../${dirname}`)
const res = readdirSync(dirpath)
const markdownFileNames = res.filter((name) => name.endsWith('.md'))
return markdownFileNames.map(v => {
return {
filename: v,
link: path.join(dirname, v)
}
})
}