pull/431/head
BeiyanYunyi 2024-09-20 23:38:58 +08:00
parent ce3fc14441
commit 37c40df9c3
No known key found for this signature in database
GPG Key ID: F2A689A496B72B62
26 changed files with 1924 additions and 4026 deletions

View File

@ -10,30 +10,29 @@
"dev:wrangler": "wrangler pages dev ./docs/.vitepress/dist/",
"build": "vitepress build docs",
"preview": "vitepress preview docs",
"update-package": "pnpm dlx vp-update",
"postinstall": "pnpm --filter @project-trans/* build"
"update-package": "pnpm dlx vp-update"
},
"devDependencies": {
"@antfu/eslint-config": "^2.21.1",
"@cloudflare/workers-types": "^4.20240620.0",
"@iconify-json/carbon": "^1.1.36",
"@iconify-json/icon-park-outline": "^1.1.15",
"@iconify-json/octicon": "^1.1.55",
"@antfu/eslint-config": "^2.27.3",
"@cloudflare/workers-types": "^4.20240919.0",
"@iconify-json/carbon": "^1.2.1",
"@iconify-json/icon-park-outline": "^1.2.0",
"@iconify-json/octicon": "^1.2.0",
"@project-trans/suggestion-box": "^0.0.9",
"@project-trans/vitepress-theme-project-trans": "workspace:*",
"@types/markdown-it": "^13.0.8",
"@project-trans/vitepress-theme-project-trans": "^0.3.1726846403",
"@types/markdown-it": "^13.0.9",
"@types/markdown-it-footnote": "^3.0.4",
"@unocss/eslint-plugin": "^0.59.4",
"eslint": "^8.57.0",
"eslint": "^8.57.1",
"eslint-plugin-format": "^0.1.2",
"simple-git": "^3.25.0",
"simple-git": "^3.27.0",
"unbuild": "^2.0.0",
"unocss": "^0.58.9",
"unplugin-vue-components": "^0.26.0",
"vite": "^5.3.1",
"vitepress": "^1.2.3",
"vitepress-sidebar": "^1.23.2",
"vue": "^3.4.30"
"vite": "^5.4.7",
"vitepress": "^1.3.4",
"vitepress-sidebar": "^1.27.0",
"vue": "^3.5.6"
},
"pnpm": {
"overrides": {

View File

@ -1,44 +0,0 @@
# VitePress Theme Project Trans
这是一个由 Project Trans 开发的 VitePress 主题。
## 使用方式
修改下述两个文件:
```typescript
// docs/.vitepress/config.ts
import genConfig from '@project-trans/vitepress-theme-project-trans/config'
import type { SidebarOptions } from '@project-trans/vitepress-theme-project-trans/theme'
import type { ThemeContext } from '@project-trans/vitepress-theme-project-trans/utils'
import { withThemeContext } from '@project-trans/vitepress-theme-project-trans/utils'
const themeConfig: ThemeContext = {
siteTitle: 'RLE.wiki',
siteDescription: '一份 RLE 指北',
/** Repo */
githubRepoLink: 'https://github.com/project-trans/RLE-wiki',
/** vitepress 根目录 */
rootDir: 'docs',
/** 文档所在目录(目前似未使用此项) */
include: ['campus', 'contributor-guide', 'fashion'],
nav,
sidebarOptions,
}
// https://vitepress.dev/reference/site-config
export default withThemeContext(themeConfig, genConfig)
```
```typescript
// docs/.vitepress/theme/index.ts
// https://vitepress.dev/guide/custom-theme
import PtjsTheme from '@project-trans/vitepress-theme-project-trans/theme'
import 'uno.css'
import './style.css'
export default {
extends: PtjsTheme,
}
```

View File

@ -1,33 +0,0 @@
import { defineBuildConfig } from 'unbuild'
export default defineBuildConfig({
entries: [
{
builder: 'mkdist',
input: './src',
outDir: './dist',
pattern: ['**/*.ts', '**/*.css'],
format: 'esm',
loaders: ['js'],
},
{
builder: 'mkdist',
input: './src',
outDir: './dist',
pattern: ['**/*.ts', '**/*.css'],
format: 'cjs',
loaders: ['js'],
},
{
builder: 'mkdist',
input: './src',
outDir: './dist',
pattern: ['**/*.vue'],
loaders: ['vue'],
},
],
declaration: true,
clean: true,
sourcemap: true,
rollup: { emitCJS: true },
})

View File

@ -1,71 +0,0 @@
{
"name": "@project-trans/vitepress-theme-project-trans",
"type": "module",
"version": "0.0.1",
"description": "A VitePress theme made with ❤️ by project-trans",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/project-trans/RLE-wiki"
},
"exports": {
"./config": {
"types": "./dist/config.d.ts",
"import": "./dist/config.mjs",
"require": "./dist/config.js"
},
"./utils": {
"types": "./dist/utils/index.d.ts",
"import": "./dist/utils/index.mjs",
"require": "./dist/utils/index.js"
},
"./theme": {
"types": "./dist/theme.d.ts",
"import": "./dist/theme.mjs",
"require": "./dist/theme.js"
},
"./components": {
"types": "./dist/components.d.ts",
"import": "./dist/components.mjs",
"require": "./dist/components.js"
}
},
"main": "./dist/config.js",
"module": "./dist/config.mjs",
"types": "./dist/config.d.ts",
"scripts": {
"build": "unbuild",
"dev": "vitepress dev",
"dev:wrangler": "wrangler pages dev ./dist/",
"preview": "vitepress preview",
"update-package": "pnpm dlx vp-update"
},
"devDependencies": {
"@antfu/eslint-config": "^2.21.1",
"@cloudflare/workers-types": "^4.20240620.0",
"@iconify-json/octicon": "^1.1.55",
"@nolebase/vitepress-plugin-enhanced-readabilities": "^2.1.2",
"@nolebase/vitepress-plugin-git-changelog": "^2.1.2",
"@nolebase/vitepress-plugin-highlight-targeted-heading": "^2.1.2",
"@nolebase/vitepress-plugin-meta": "^2.2.1",
"@project-trans/suggestion-box": "^0.0.9",
"@types/markdown-it": "^14.1.1",
"@types/markdown-it-footnote": "^3.0.4",
"@types/node": "^20.14.8",
"@unocss/eslint-plugin": "^0.61.0",
"eslint": "^8.57.0",
"eslint-plugin-format": "^0.1.2",
"markdown-it-footnote": "^4.0.0",
"markdown-it-katex": "^2.0.3",
"markdown-it-pangu": "^1.0.2",
"md5": "^2.3.0",
"simple-git": "^3.25.0",
"unocss": "^0.61.0",
"unplugin-vue-components": "^0.27.0",
"vite": "^5.3.1",
"vitepress": "^1.2.3",
"vitepress-sidebar": "^1.23.2",
"vue": "^3.4.30",
"wrangler": "^3.61.0"
}
}

View File

@ -1,50 +0,0 @@
<script lang="ts" setup>
import DefaultTheme from 'vitepress/theme'
import {
NolebaseHighlightTargetedHeading,
} from '@nolebase/vitepress-plugin-highlight-targeted-heading/client'
import {
NolebaseEnhancedReadabilitiesMenu,
NolebaseEnhancedReadabilitiesScreenMenu,
} from '@nolebase/vitepress-plugin-enhanced-readabilities/client'
import { useData } from 'vitepress'
import CopyrightInfo from './components/CopyrightInfo.vue'
import AppFooter from './components/AppFooter.vue'
import AppearanceToggle from './components/AppearanceToggle.vue'
import PageInfo from './components/PageInfo.vue'
const { Layout } = DefaultTheme
const { frontmatter } = useData()
</script>
<template>
<AppearanceToggle>
<Layout>
<template #doc-before>
<NolebaseHighlightTargetedHeading />
<div class="vp-doc vp-doc-before">
<h1>{{ frontmatter.title }}</h1>
<PageInfo />
<CopyrightInfo />
</div>
</template>
<template #doc-after>
<AppFooter />
</template>
<template #nav-bar-content-after>
<NolebaseEnhancedReadabilitiesMenu />
</template>
<template #nav-screen-content-after>
<NolebaseEnhancedReadabilitiesScreenMenu />
</template>
</Layout>
</AppearanceToggle>
</template>
<style>
.vp-doc-before + main > div > div > h2:first-of-type {
border-top: unset;
margin-top: 0;
}
</style>

View File

@ -1,19 +0,0 @@
import AppearanceToggle from './components/AppearanceToggle.vue'
import AppFooter from './components/AppFooter.vue'
import AppSBox from './components/AppSBox.vue'
import ArticlesMenu from './components/ArticlesMenu.vue'
import CopyrightInfo from './components/CopyrightInfo.vue'
import HomeContent from './components/HomeContent.vue'
import PageInfo from './components/PageInfo.vue'
import ReadingTime from './components/ReadingTime.vue'
export {
AppFooter,
AppSBox,
AppearanceToggle,
ArticlesMenu,
CopyrightInfo,
HomeContent,
PageInfo,
ReadingTime,
}

View File

@ -1,39 +0,0 @@
<script setup lang="ts">
import { ref, watch, } from 'vue';
import { NolebaseGitChangelog } from '@nolebase/vitepress-plugin-git-changelog/client';
import { useRoute } from 'vitepress';
const route = useRoute();
// ref key
const componentKey = ref(0);
const frontmatter = ref({});
const isFrontmatterLoaded = ref(false);
// key frontmatter
const updateKeyAndFrontmatter = () => {
componentKey.value += 1;
frontmatter.value = route.data?.frontmatter || {};
isFrontmatterLoaded.value = true;
};
// key frontmatter
watch(() => route.path, () => {
isFrontmatterLoaded.value = false;
updateKeyAndFrontmatter();
}, { immediate: true }); // key frontmatter
// key frontmatter
// onMounted(updateKeyAndFrontmatter);
</script>
<template>
<div :key="componentKey" class="vp-doc">
<h2 id="意见反馈">
意见反馈
</h2>
<AppSBox />
<!-- 仅在 Frontmatter 加载完成且未设置 hideChangelog 时渲染 GitChangelog -->
<NolebaseGitChangelog v-if="isFrontmatterLoaded && !frontmatter.hideChangelog" />
</div>
</template>

View File

@ -1,11 +0,0 @@
<script setup lang="ts">
import SuggestionBox from '@project-trans/suggestion-box'
import '@project-trans/suggestion-box/dist/style.css'
</script>
<template>
<SuggestionBox
text-content-placeholder="这个页面有什么问题?或者有什么建议?请在下方留言,我们会尽快回复和处理。"
target-url="/api/v1/suggestion"
/>
</template>

View File

@ -1,76 +0,0 @@
<script setup lang="ts">
import { useData } from 'vitepress'
import { nextTick, provide } from 'vue'
const { isDark } = useData()
const isSSR = typeof window === 'undefined'
function enableTransitions() {
return isSSR
? false
: 'startViewTransition' in document
&& window.matchMedia('(prefers-reduced-motion: no-preference)').matches
}
provide('toggle-appearance', async ({ clientX: x, clientY: y }: MouseEvent) => {
if (!enableTransitions()) {
isDark.value = !isDark.value
return
}
if (!document.documentElement.classList.contains('VPSwitchAppearance-ViewTransition'))
document.documentElement.classList.add('VPSwitchAppearance-ViewTransition')
const clipPath = [
`circle(0px at ${x}px ${y}px)`,
`circle(${Math.hypot(
Math.max(x, innerWidth - x),
Math.max(y, innerHeight - y),
)}px at ${x}px ${y}px)`,
]
await (document as any).startViewTransition(async () => {
isDark.value = !isDark.value
await nextTick()
}).ready
document.documentElement.animate(
{ clipPath: isDark.value ? clipPath.reverse() : clipPath },
{
duration: 500,
easing: 'ease-in',
pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)`,
},
)
})
</script>
<template>
<slot />
</template>
<style>
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
}
::view-transition-old(root),
.dark::view-transition-new(root) {
z-index: 1;
}
::view-transition-new(root),
.dark::view-transition-old(root) {
z-index: 9999;
}
.VPSwitchAppearance-ViewTransition .VPSwitchAppearance .check {
transition: transform 350ms 0ms !important;
}
.VPSwitchAppearance-ViewTransition.dark .VPSwitchAppearance .check {
transition: transform 350ms 500ms !important;
}
</style>

View File

@ -1,32 +0,0 @@
<script setup lang="ts">
import { useRoute } from 'vitepress'
import { computed } from 'vue'
import { data } from './articlesmenu.data'
const route = useRoute()
const articles = computed(() =>
data
.filter((article) => {
if (!article.url.startsWith(route.path))
return false
if (article.url === route.path)
return false
const relateUrl = article.url.replace(route.path, '')
const slashCount = relateUrl.split('/').length - 1
if (slashCount > 1)
return false
if (slashCount === 1 && !relateUrl.endsWith('/'))
return false
return true
})
.map(article => ({ link: article.url, text: article.title })),
)
</script>
<template>
<ul>
<li v-for="article in articles" :key="article.link">
<a :href="article.link">{{ article.text }}</a>
</li>
</ul>
</template>

View File

@ -1,68 +0,0 @@
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed } from 'vue'
import type { Node, Trie } from '../plugins/CopyrightLoader.data'
import { data } from '../plugins/CopyrightLoader.data'
function searchClosestInTrie(
that: Trie<Record<string, any>>,
path: string[],
node: Node<Record<string, any>> = that.root,
): Record<string, any> | null {
if (path.length === 0)
return node.value
if (path[0] in node.children) {
let value = searchClosestInTrie(
that,
path.slice(1),
node.children[path[0]],
)
if (value === null)
value = node.value
return value
}
return node.value
}
const paths = useData()
.page.value.relativePath.replace('.md', '').split('/')
.filter((item: string) => item !== '')
const attrs = computed(() => searchClosestInTrie(data, paths))
const frontmatter = useData().frontmatter
const originUrlExists = computed(() => (attrs.value?.copyright?.url ?? null) != null)
const originUrl = computed(() => attrs.value?.copyright?.url ?? 'javascript:void(0)')
const license = computed(() => attrs.value?.copyright?.license ?? null)
const licenseExists = computed(() => license.value != null)
const licenseUrlExists = computed(() => (attrs.value?.copyright?.licenseUrl ?? null) != null)
const licenseUrl = computed(() => attrs.value?.copyright?.licenseUrl ?? 'javascript:void(0)')
</script>
<template>
<div v-if="attrs?.copyright?.enable ?? false">
<div class="tip custom-block">
<p class="custom-block-title">
Copyright
</p>
<p>
<span>这篇文章 </span>
<a v-if="originUrlExists" :href="originUrl">{{ frontmatter.title }}</a>
<span v-else>{{ frontmatter.title }}</span>
<span> </span>
<span v-for="author in attrs?.author" :key="author">{{ author }}</span>
<span> 创作</span>
<span v-if="licenseExists">
<span>Project Trans </span>
<a v-if="licenseUrlExists" :href="licenseUrl">{{ license }}</a>
<span v-else>{{ license }}</span>
<span> 许可下使用</span>
</span>
<span></span>
</p>
</div>
<hr>
</div>
</template>

View File

@ -1,27 +0,0 @@
<template>
<div class="vp-doc container">
<slot />
</div>
</template>
<style scoped>
.container {
margin: auto;
width: 100%;
max-width: 1280px;
padding: 0 24px;
}
@media (min-width: 640px) {
.container {
padding: 0 48px;
}
}
@media (min-width: 960px) {
.container {
width: 100%;
padding: 0 64px;
}
}
</style>

View File

@ -1,70 +0,0 @@
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed, onMounted, ref, watchEffect } from 'vue'
import ReadingTime from './ReadingTime.vue' // ReadingTime
// VitePress
const { frontmatter, page, theme, lang } = useData()
//
const date = computed(
() => new Date(frontmatter.value.lastUpdated ?? page.value.lastUpdated),
)
// ISO
const isoDatetime = computed(() => date.value.toISOString())
//
const datetime = ref('')
// hydration
onMounted(() => {
watchEffect(() => {
// 使 API
datetime.value = new Intl.DateTimeFormat(
theme.value.lastUpdated?.formatOptions?.forceLocale ? lang.value : undefined,
theme.value.lastUpdated?.formatOptions ?? {
dateStyle: 'short',
timeStyle: 'short',
},
).format(date.value)
})
})
//
const authors = computed(() => {
let author = (frontmatter.value?.author ?? []) as string[]
if (!Array.isArray(author))
author = [author]
return author
})
//
const displayAuthors = computed(() => {
if (authors.value.length === 0) {
return '匿名'
}
else {
return `${authors.value.join(', ')}`
}
})
</script>
<template>
<div class="mb-10 mt-4 flex flex-wrap gap-4">
<div class="inline-flex items-center gap-1">
<span class="i-octicon:person" />
<span>作者:</span>
<span>{{ displayAuthors }}</span>
</div>
<div class="inline-flex items-center gap-1">
<span class="i-octicon:calendar-16" />
<span>{{ theme.lastUpdated?.text || 'Last updated' }}:</span>
<time :datetime="isoDatetime">{{ datetime }}</time>
</div>
<ClientOnly>
<ReadingTime /> <!-- 添加 ReadingTime 组件 -->
</ClientOnly>
</div>
</template>

View File

@ -1,26 +0,0 @@
<script setup lang="ts">
import { useData } from 'vitepress'
//
const { frontmatter } = useData()
//
function calculateReadingTime(wordCount: number) {
const wordsPerMinute = 500 // 500
return Math.ceil(wordCount / wordsPerMinute) //
}
// frontmatter
const wordCount = frontmatter.value.wordCount || 0
const readingTime = calculateReadingTime(wordCount)
</script>
<template>
<div>
<p>字数: {{ wordCount }} &nbsp; 预计阅读时间: {{ readingTime }} 分钟</p>
</div>
</template>
<style scoped>
/* 这里可以添加样式 */
</style>

View File

@ -1,10 +0,0 @@
import { createContentLoader } from 'vitepress'
declare const data: { url: string, title: string }[]
export { data }
export default createContentLoader('**/*.md', {
transform: list =>
list.map(item => ({ url: item.url, title: item.frontmatter.title })),
})

View File

@ -1,230 +0,0 @@
import fs from 'node:fs'
import path, { dirname, resolve } from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { GitChangelog } from '@nolebase/vitepress-plugin-git-changelog/vite'
import { transformHeadMeta } from '@nolebase/vitepress-plugin-meta'
import footnote from 'markdown-it-footnote'
import katex from 'markdown-it-katex'
import mdPangu from 'markdown-it-pangu'
import UnoCSS from 'unocss/vite'
import Components from 'unplugin-vue-components/vite'
import { defineConfig } from 'vitepress'
import { generateSidebar } from './sidebar'
import { useThemeContext } from './utils/themeContext'
// 从文件系统读取 Markdown 文件内容
function readMarkdownFileContent(filePath: string): string {
if (fs.existsSync(filePath)) {
return fs.readFileSync(filePath, 'utf-8')
}
return ''
}
// 统计文档的字数函数
function countWords(content: string): number {
const cleanedContent = content
.replace(/```[\s\S]*?```/g, '') // 移除代码块
.replace(/!\[.*?\]\(.*?\)/g, '') // 移除图片链接
.replace(/\[.*?\]\(.*?\)/g, '') // 移除普通链接
.replace(/<[^>]+(>|$)/g, '') // 移除 HTML 标签
.replace(/[.,/#!$%^&*;:{}=\-_`~()]/g, '') // 移除标点符号
.replace(/\s+/g, ' ') // 将多余的空格归为一个空格
.trim() // 去除首尾空格
const chineseCharacters = cleanedContent.match(/[\u4E00-\u9FFF\uFF01-\uFFE5]/g) || []
const words = cleanedContent.split(/\s+/).filter(Boolean)
return chineseCharacters.length + words.length
}
// https://vitepress.dev/reference/site-config
function genConfig() {
const themeConfig = useThemeContext()
const { siteTitle, githubRepoLink, nav }
= themeConfig
return defineConfig({
lang: 'zh-CN',
title: siteTitle,
cleanUrls: true,
markdown: {
config(md) {
md.use(mdPangu)
md.use(footnote)
md.use(katex)
},
},
head: [
[
'link',
{
rel: 'apple-touch-icon',
sizes: '180x180',
href: '/apple-touch-icon.png',
},
],
[
'link',
{
rel: 'icon',
type: 'image/png',
sizes: '32x32',
href: '/favicon-32x32.png',
},
],
[
'link',
{
rel: 'icon',
type: 'image/png',
sizes: '16x16',
href: '/favicon-16x16.png',
},
],
['link', { rel: 'manifest', href: '/site.webmanifest' }],
['meta', { name: 'msapplication-TileColor', content: '#4c4c4c' }],
['meta', { name: 'theme-color', content: '#ffffff' }],
['meta', { property: 'og:site_name', content: siteTitle }],
],
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
siteTitle: false,
logo: {
src: '/logo-horizontal.svg',
alt: 'Logo: RLE.wiki',
},
nav,
sidebar: generateSidebar(),
socialLinks: [{ icon: 'github', link: githubRepoLink }],
editLink: {
pattern: `${githubRepoLink}/edit/main/docs/:path`,
text: '在 GitHub 上编辑此页面', // label localization
},
// label localization
outline: { label: '本页大纲', level: 'deep' },
lastUpdated: { text: '最后更新' },
darkModeSwitchLabel: '深色模式',
sidebarMenuLabel: '目录',
returnToTopLabel: '返回顶部',
docFooter: {
prev: '上一页',
next: '下一页',
},
search: {
provider: 'local',
options: {
locales: {
root: {
translations: {
button: {
buttonText: '搜索文档',
buttonAriaLabel: '搜索文档',
},
modal: {
noResultsText: '无法找到相关结果',
resetButtonTitle: '清除查询条件',
displayDetails: '显示详细列表',
footer: {
selectText: '选择',
navigateText: '切换',
closeText: '关闭',
// 无障碍ARIA标签用于描述键盘导航操作
navigateUpKeyAriaLabel: '上箭头',
navigateDownKeyAriaLabel: '下箭头',
selectKeyAriaLabel: '回车',
closeKeyAriaLabel: '退出',
},
},
},
},
},
// Add title field in frontmatter to search
// You can exclude a page from search by adding search: false to the page's frontmatter.
_render(src, env, md) {
if (env.frontmatter?.search === false)
return ''
let html = md.render(src, env)
if (env.frontmatter?.title)
html = md.render(`# ${env.frontmatter.title}\n`) + html
return html
},
},
},
},
transformHead: async (context) => {
let head = [...context.head]
const returnedHead = await transformHeadMeta()(head, context)
if (typeof returnedHead !== 'undefined')
head = returnedHead
return head
},
vite: {
plugins: [
GitChangelog({
repoURL: githubRepoLink,
}),
// GitChangelogMarkdownSection({
// sections: {
// disableChangelog: false,
// disableContributors: true,
// },
// getChangelogTitle: (): string => {
// return '文件历史'
// },
// excludes: [],
// exclude: (_, { helpers }): boolean => {
// if (helpers.idEquals('index.md'))
// return true
// return false
// },
// }),
Components({
dirs: [
'docs/.vitepress/theme/components',
resolve(
typeof dirname(fileURLToPath(import.meta.url)) === 'string'
? dirname(fileURLToPath(import.meta.url))
: __dirname,
'./components',
),
],
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
dts: './.vitepress/components.d.ts',
transformer: 'vue3',
}),
UnoCSS(),
],
ssr: {
noExternal: [
'@nolebase/vitepress-plugin-enhanced-readabilities',
'@nolebase/vitepress-plugin-highlight-targeted-heading',
],
},
},
transformPageData(pageData) {
// 构建 Markdown 文件路径
const markdownFile = `${pageData.relativePath}`
const filePath = path.join(process.cwd(), 'docs', markdownFile)
// 从文件系统读取文件内容
const content = readMarkdownFileContent(filePath)
// 统计字数并插入到 Frontmatter
const wordCount = countWords(content)
return {
frontmatter: {
...pageData.frontmatter,
wordCount, // 将字数写入 Frontmatter
},
}
},
})
}
export default genConfig

View File

@ -1,4 +0,0 @@
:root {
--vp-font-family-base: sans-serif;
--vp-font-family-mono: monospace;
}

View File

@ -1,62 +0,0 @@
import type { ContentData } from 'vitepress'
import { createContentLoader, defineLoader } from 'vitepress'
const contentLoader = createContentLoader('/**/*.md')
export interface Node<T> {
value: T | null
children: { [key: string]: Node<T> }
}
export interface Trie<T> {
root: Node<T>
insert: ((path: string[], value: T, node: Node<T>) => void) & ((path: string[], value: T) => void)
}
declare const data: Trie<Record<string, any>>
export { data }
// noinspection JSUnusedGlobalSymbols
export default defineLoader({
watch: contentLoader.watch,
async load(): Promise<Trie<Record<string, any>>> {
const raw: ContentData[] = await contentLoader.load()
const trie: Trie<Record<string, any>> = {
root: { value: null, children: {} },
insert(this: Trie<Record<string, any>>, path, value, node: Node<Record<string, any>> = this.root) {
if (path.length === 0) {
node.value = value
}
else if (path.length === 1) {
if (!(path[0] in node.children))
node.children[path[0]] = { value, children: {} }
else
node.children[path[0]].value = value
}
else {
if (!(path[0] in node.children)) {
const new_node = { value: null, children: {} }
this.insert(path.slice(1), value, new_node)
node.children[path[0]] = new_node
}
else {
this.insert(path.slice(1), value, node.children[path[0]])
}
}
},
}
for (const v of raw) {
const frontmatter = v.frontmatter ?? null
const copyright = frontmatter?.copyright ?? null
if (copyright !== null) {
trie.insert(v.url.split('/').filter((item, _index, _array) => item !== ''), {
author: frontmatter.author ?? null,
title: frontmatter.title ?? null,
copyright,
})
}
}
return trie
},
})

View File

@ -1,29 +0,0 @@
import type {
SidebarItem,
SidebarMultiItem,
} from 'vitepress-sidebar'
import { generateSidebar as genSidebar } from 'vitepress-sidebar'
import { useThemeContext } from './utils/themeContext'
export function generateSidebar() {
const { sidebarOptions } = useThemeContext()
const sidebar = genSidebar(sidebarOptions)
for (const key in sidebar) {
const sidebarMultiItem: SidebarMultiItem = (sidebar as any)[key]
sidebarMultiItem.items.sort(sidebarTitleSorter)
}
return sidebar
}
function sidebarTitleSorter(infoA: SidebarItem, infoB: SidebarItem): number {
const textA = infoA.text
const textB = infoB.text
if (textA === undefined || textB === undefined)
return 0
const infoANfc = textA.normalize('NFC')
const infoBNfc = textB.normalize('NFC')
return infoANfc.localeCompare(infoBNfc, 'zh', {
numeric: true,
})
}

View File

@ -1,42 +0,0 @@
// https://vitepress.dev/guide/custom-theme
import type { Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme-without-fonts'
import { h } from 'vue'
import './custom-font.css'
import {
NolebaseEnhancedReadabilitiesPlugin,
} from '@nolebase/vitepress-plugin-enhanced-readabilities/client'
import {
NolebaseGitChangelogPlugin,
} from '@nolebase/vitepress-plugin-git-changelog/client'
import type Options from 'vitepress-sidebar'
import '@nolebase/vitepress-plugin-enhanced-readabilities/client/style.css'
import '@nolebase/vitepress-plugin-git-changelog/client/style.css'
import '@nolebase/vitepress-plugin-highlight-targeted-heading/client/style.css'
import Layout from './Layout.vue'
export type SidebarOptions = Options
export default {
extends: DefaultTheme,
Layout,
enhanceApp({ app }) {
app.use(NolebaseEnhancedReadabilitiesPlugin, {
spotlight: {
defaultToggle: true,
},
})
app.use(NolebaseGitChangelogPlugin, {
locales: {
'zh-CN': {
lastEditedDateFnsLocaleName: 'zhCN',
},
},
})
},
} satisfies Theme

View File

@ -1,2 +0,0 @@
export * from './path'
export * from './themeContext'

View File

@ -1,13 +0,0 @@
import { normalizePath } from 'vite'
export function pathEquals(path: string, equals: string): boolean {
return normalizePath(path) === (normalizePath(equals))
}
export function pathStartsWith(path: string, startsWith: string): boolean {
return normalizePath(path).startsWith(normalizePath(startsWith))
}
export function pathEndsWith(path: string, startsWith: string): boolean {
return normalizePath(path).endsWith(normalizePath(startsWith))
}

View File

@ -1,26 +0,0 @@
import { AsyncLocalStorage } from 'node:async_hooks'
import type { DefaultTheme } from 'vitepress'
import type { generateSidebar } from 'vitepress-sidebar'
import type Options from 'vitepress-sidebar'
type NavConfig = DefaultTheme.Config['nav']
export interface ThemeContext {
siteTitle: string
siteDescription: string
githubRepoLink: string
rootDir: string
include: string[]
nav: NavConfig
sidebarOptions: Options | Options[]
}
const themeContext = new AsyncLocalStorage<ThemeContext>()
export function withThemeContext<T>(context: ThemeContext, fn: () => T): T {
return themeContext.run(context, fn)
}
export function useThemeContext(): ThemeContext {
return themeContext.getStore()!
}

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,2 @@
packages:
- functions
- packages/*

View File

@ -1,5 +1,5 @@
import { defineConfig, presetAttributify, presetIcons, presetUno } from 'unocss';
import presetSBox from '@project-trans/suggestion-box/dist/preset';
import { defineConfig, presetAttributify, presetIcons, presetUno } from 'unocss'
import presetSBox from '@project-trans/suggestion-box/dist/preset'
export default defineConfig({
shortcuts: [