feat: add initial OpenWiki structure with custom theme and scripts

This commit is contained in:
2026-06-02 13:20:51 +08:00
parent b9bbcf131e
commit 0b4b79a1e6
16 changed files with 489 additions and 0 deletions

View File

@@ -0,0 +1,158 @@
import { defineConfig } from "vitepress";
import { vitepressBeautifulMermaid } from "v-beautiful-mermaid";
import { readdirSync, statSync, readFileSync } from "fs";
import { join } from "path";
import { load } from "js-yaml";
const siteConfig = load(
readFileSync(join(__dirname, "../config.yaml"), "utf8"),
) as {
title?: string;
description?: string;
logo?: string;
siteTitle?: false;
appearance?: false;
nav?: { text: string; link: string }[];
footer?: { message: string; copyright: string };
};
interface SidebarItem {
text: string;
link?: string;
items?: SidebarItem[];
}
function isDirectory(path: string): boolean {
try {
return statSync(path).isDirectory();
} catch {
return false;
}
}
function buildSidebarTree(root: string, basePath: string = ""): SidebarItem[] {
const items: SidebarItem[] = [];
const entries = readdirSync(root);
// Separate files and directories
const files: string[] = [];
const dirs: string[] = [];
for (const entry of entries) {
if (entry === "index.md") continue;
const fullPath = join(root, entry);
if (isDirectory(fullPath)) {
dirs.push(entry);
} else if (entry.endsWith(".md")) {
files.push(entry);
}
}
// Add files first
for (const f of files.sort()) {
const name = f.replace(/\.md$/, "");
const linkPath = `/${root}/${name}`;
items.push({
text: name,
link: linkPath,
});
}
// Add directories as submenus
for (const d of dirs.sort()) {
const subRoot = join(root, d);
const subBasePath = basePath ? `${basePath}/${d}` : d;
const subItems = buildSidebarTree(subRoot, subBasePath);
if (subItems.length > 0) {
items.push({
text: d,
items: subItems,
});
}
}
return items;
}
function buildSidebarItems(root: string): SidebarItem[] {
return buildSidebarTree(root);
}
const sidebar = {
"/": [
{
items: [
{
text: "主页",
link: "/",
},
{
text: "概览",
link: "/overview",
},
{
text: "变更日志",
link: "/log",
},
],
},
{
items: [{}],
},
],
"/docs/": [
{
text: "记录",
items: [{}],
},
{
text: "工作空间",
items: [{}],
},
],
"/sources/": [
{
items: [{}],
},
],
};
sidebar["/docs/"][0].items = buildSidebarItems("docs/notes");
sidebar["/docs/"][1].items = buildSidebarItems("docs/subspaces");
sidebar["/sources/"][0].items = buildSidebarItems("sources");
sidebar["/"][1].items = buildSidebarItems("nodes");
// https://vitepress.dev/reference/site-config
export default defineConfig({
title: siteConfig.title,
description: siteConfig.description,
lang: "zh-CN",
base: "/", // Replace with your repository name if deploying to GitHub Pages
srcDir: ".",
ignoreDeadLinks: true,
appearance: siteConfig.appearance,
themeConfig: {
logo: siteConfig.logo,
siteTitle: siteConfig.siteTitle,
nav: [
{ text: "知识库", link: "/overview" },
{ text: "资料库", link: "/sources/" },
{ text: "文档", link: "/docs/" },
],
sidebar,
footer: siteConfig.footer,
search: {
provider: "local",
},
},
markdown: {
config: (md) => {
md.use(vitepressBeautifulMermaid, {
colorMode: "auto",
renderOptions: {
font: "LXGW WenKai Mono",
},
});
},
},
});

View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useData } from 'vitepress'
import DefaultTheme from 'vitepress/theme-without-fonts'
import DocTags from './components/DocTags.vue'
import DocMetaEx from './components/DocMetaEx.vue'
const { frontmatter } = useData()
const tags = computed(() => frontmatter.value?.tags ?? [])
const hasTags = computed(() => tags.value.length > 0)
</script>
<template>
<DefaultTheme.Layout>
<template #doc-before>
<DocTags v-if="hasTags" style="margin-bottom: 24px;" />
</template>
<template #aside-outline-after>
<DocMetaEx style="margin-top: 24px; padding-left: 16px;"/>
</template>
</DefaultTheme.Layout>
</template>

View File

@@ -0,0 +1,50 @@
<script setup lang="ts">
import { computed } from "vue";
import { useData } from "vitepress";
const { frontmatter } = useData();
const source = computed(() => frontmatter.value?.source ?? null);
const title = computed(() => frontmatter.value?.title ?? null);
const abstract = computed(() => frontmatter.value?.abstract ?? null);
const hasExtended = computed(
() => source.value || title.value || abstract.value,
);
</script>
<template>
<div v-if="hasExtended" class="doc-meta-ex">
<div v-if="title" class="doc-meta-title">标题:{{ title }}</div>
<div v-if="source" class="doc-meta-source">
来源:<a :href="source" target="_blank" rel="noopener noreferrer">{{
source
}}</a>
</div>
<div v-if="abstract" class="doc-meta-abstract">摘要:{{ abstract }}</div>
</div>
</template>
<style scoped>
.doc-meta-ex {
display: flex;
flex-direction: column;
line-height: 1.8;
gap: 8px;
}
.doc-meta-source {
font-size: 12px;
color: var(--vp-c-text-2);
}
.doc-meta-title {
font-size: 12px;
color: var(--vp-c-text-1);
}
.doc-meta-abstract {
font-size: 12px;
color: var(--vp-c-text-2);
}
</style>

View File

@@ -0,0 +1,90 @@
<script setup lang="ts">
import { computed } from "vue";
import { useData } from "vitepress";
const { frontmatter } = useData();
const tags = computed(() => frontmatter.value?.tags ?? []);
const aliases = computed(() => frontmatter.value?.aliases ?? []);
const createdAt = computed(() => frontmatter.value?.created_at ?? null);
const updatedAt = computed(() => frontmatter.value?.updated_at ?? null);
const hasTags = computed(() => tags.value.length > 0);
const hasTime = computed(() => createdAt.value || updatedAt.value);
function formatDate(date: string | null): string {
if (!date) return "";
const d = new Date(date);
if (isNaN(d.getTime())) return date as string;
return d.toLocaleDateString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
});
}
</script>
<template>
<div class="doc-tags-wrapper">
<div v-if="hasTime" class="doc-meta-time">
<span v-if="createdAt">创建于 {{ formatDate(createdAt) }}</span>
<span v-if="createdAt && updatedAt"> · </span>
<span v-if="updatedAt">更新于 {{ formatDate(updatedAt) }}</span>
</div>
<div v-if="hasTags" class="doc-tags">
<span v-for="tag in tags" :key="tag" class="doc-tag"># {{ tag }}</span>
</div>
<div v-if="aliases.length > 0" class="doc-meta-aliases">
<span v-for="alias in aliases" :key="alias" class="doc-meta-alias">{{
alias
}}</span>
</div>
</div>
</template>
<style scoped>
.doc-tags-wrapper {
display: flex;
flex-direction: column;
gap: 8px;
}
.doc-meta-time {
font-size: 12px;
color: var(--vp-c-text-3);
}
.doc-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.doc-meta-aliases {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.doc-meta-alias {
display: inline-block;
padding: 0 10px;
line-height: 22px;
font-size: 12px;
font-weight: 500;
border-radius: 12px;
background-color: var(--vp-c-bg-soft);
color: var(--vp-c-brand-1);
border: 1px solid var(--vp-c-brand-1);
}
.doc-tag {
display: inline-block;
padding: 0 10px;
line-height: 22px;
font-size: 12px;
font-weight: 500;
border-radius: 12px;
background-color: var(--vp-c-bg-soft);
color: var(--vp-c-text-2);
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,22 @@
@import url("https://fontsapi.zeoseven.com/292/main/result.css");
@import url("https://fontsapi.zeoseven.com/292/light/result.css");
@import url("https://fontsapi.zeoseven.com/293/main/result.css");
@import url("https://fontsapi.zeoseven.com/2/main/result.css");
@import url("https://fontsapi.zeoseven.com/147/main/result.css");
:root {
--vp-font-family-base: "LXGW WenKai";
--vp-font-family-mono: "LXGW WenKai Mono";
}
strong {
font-family: "LXGW ZhenKai GB";
}
em {
font-family: "LXGW WenKai Light";
}
h1 {
font-family: "JiangChengLvDongSong";
}

View File

@@ -0,0 +1,16 @@
import DefaultTheme from "vitepress/theme-without-fonts";
import { installMermaid } from "v-beautiful-mermaid/client";
import Viewer from "./components/Viewer.vue";
import Layout from "./Layout.vue";
import "v-beautiful-mermaid/style.css";
import "./fonts.css";
/** @type {import('vitepress').Theme} */
export default {
extends: DefaultTheme,
Layout,
enhanceApp({ app }) {
installMermaid(app);
app.component("Viewer", Viewer);
},
};