Refactor/split config (#360)
部署文档 / build (push) Has been cancelled Details

Signed-off-by: Anna Beiyan <131662099+AnnaBeiyan@users.noreply.github.com>
Co-authored-by: Anna Beiyan <131662099+AnnaBeiyan@users.noreply.github.com>
Co-authored-by: Neko Ayaka <neko@ayaka.moe>
pull/358/head
北雁云依 2024-06-24 00:14:35 +08:00 committed by GitHub
parent 25eaa2ba69
commit d31461684e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 6860 additions and 4307 deletions

View File

@ -1,18 +1,18 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AppearanceToggle: typeof import('./theme/components/AppearanceToggle.vue')['default']
AppFooter: typeof import('./theme/components/AppFooter.vue')['default']
AppSBox: typeof import('./theme/components/AppSBox.vue')['default']
ArticlesMenu: typeof import('./theme/components/ArticlesMenu.vue')['default']
CopyrightInfo: typeof import('./theme/components/CopyrightInfo.vue')['default']
HomeContent: typeof import('./theme/components/HomeContent.vue')['default']
PageInfo: typeof import('./theme/components/PageInfo.vue')['default']
AppearanceToggle: typeof import('./../../packages/vitepress-theme-project-trans/dist/components/AppearanceToggle.vue')['default']
AppFooter: typeof import('./../../packages/vitepress-theme-project-trans/dist/components/AppFooter.vue')['default']
AppSBox: typeof import('./../../packages/vitepress-theme-project-trans/dist/components/AppSBox.vue')['default']
ArticlesMenu: typeof import('./../../packages/vitepress-theme-project-trans/dist/components/ArticlesMenu.vue')['default']
CopyrightInfo: typeof import('./../../packages/vitepress-theme-project-trans/dist/components/CopyrightInfo.vue')['default']
HomeContent: typeof import('./../../packages/vitepress-theme-project-trans/dist/components/HomeContent.vue')['default']
PageInfo: typeof import('./../../packages/vitepress-theme-project-trans/dist/components/PageInfo.vue')['default']
}
}

View File

@ -1,202 +1,101 @@
import { defineConfig } from 'vitepress'
import nav from './nav'
import mdPangu from 'markdown-it-pangu'
import katex from 'markdown-it-katex'
import footnote from 'markdown-it-footnote'
import { sidebar } from './sidebar'
import { rootDir, githubRepoLink } from './meta'
import { readFileSync, statSync } from 'node:fs'
import { join } from 'node:path'
const siteTitle = 'RLE.wiki'
const siteDescription = '一份 RLE 指北'
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'
import type { DefaultTheme } from 'vitepress'
type NavConfig = DefaultTheme.Config['nav']
const nav: NavConfig = [
{
text: '大学指南',
link: '/campus/',
},
{
text: '时尚护理',
link: '/fashion/',
},
{
text: '安全防护',
link: '/personal-safety/',
},
{
text: '其它',
link: '/others/',
},
{
text: '贡献指南',
items: [
{
text: '校园版块投稿指南',
link: '/contributor-guide/campus.md',
},
{
text: '其他投稿指南',
link: '/contributor-guide/other.md',
},
{
text: '校园版块贡献模板',
link: '/contributor-guide/CampusTemplate.md',
},
],
},
]
const baseConfig = {
useTitleFromFrontmatter: true,
useFolderTitleFromIndexFile: true,
useFolderLinkFromIndexFile: true,
excludeFilesByFrontmatterFieldName: true,
collapsed: true,
documentRootPath: '/docs',
} satisfies Partial<SidebarOptions>
const sidebarOptions = [
// 大学指南
{
...baseConfig,
scanStartPath: 'campus',
resolvePath: '/campus/',
},
// 贡献指南
{
...baseConfig,
scanStartPath: 'contributor-guide',
resolvePath: '/contributor-guide/',
},
// 时尚护理
{
...baseConfig,
scanStartPath: 'fashion',
resolvePath: '/fashion/',
},
// 安全防护
{
...baseConfig,
scanStartPath: 'personal-safety',
resolvePath: '/personal-safety/',
},
// 其它
{
...baseConfig,
scanStartPath: 'others',
resolvePath: '/others/',
},
]
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 defineConfig({
lang: 'zh-CN',
title: siteTitle,
cleanUrls: true,
markdown: {
config(md) {
md.use(mdPangu);
md.use(footnote);
md.use(katex);
},
},
dir: rootDir,
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,
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: '清除查询条件',
footer: {
selectText: '选择',
navigateText: '切换',
},
},
},
},
},
// 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: (context) => {
const head = [...context.head] || []
const pageSourceFilePath = join(rootDir, context.pageData.filePath)
const pageSourceFileStat = statSync(join(rootDir, context.pageData.filePath))
if (pageSourceFileStat.isDirectory()) {
head.push([
'meta',
{
property: 'og:title',
content: siteTitle,
},
])
head.push([
'meta',
{
name: 'description',
content: siteDescription,
},
])
return head
}
let pageSourceFileContent = readFileSync(pageSourceFilePath, { encoding: 'utf-8' })
// remove all frontmatter
pageSourceFileContent = pageSourceFileContent.replace(/---[\s\S]*?---/, '')
// remove markdown heading markup but keep the text content
pageSourceFileContent = pageSourceFileContent.replace(/^(#+)\s+(.*)/gm, ' $2 ')
// remove markdown link markup but keep the text content
pageSourceFileContent = pageSourceFileContent.replace(/\[([^\]]+)\]\([^)]+\)/gm, ' $1 ')
// remove markdown image markup but keep the text content
pageSourceFileContent = pageSourceFileContent.replace(/\!\[([^\]]+)\]\([^)]+\)/gm, ' $1 ')
// remove markdown reference link markup but keep the text content
pageSourceFileContent = pageSourceFileContent.replace(/\[.*]/gm, '')
// remove markdown bold markup but keep the text content
pageSourceFileContent = pageSourceFileContent.replace(/\*\*([^*]+)\*\*/gm, ' $1 ')
pageSourceFileContent = pageSourceFileContent.replace(/__([^*]+)__/gm, ' $1 ')
// remove markdown italic markup but keep the text content
pageSourceFileContent = pageSourceFileContent.replace(/\*([^*]+)\*/gm, ' $1 ')
pageSourceFileContent = pageSourceFileContent.replace(/_([^*]+)_/gm, ' $1 ')
// remove markdown code markup but keep the text content
pageSourceFileContent = pageSourceFileContent.replace(/`([^`]+)`/gm, ' $1 ')
// remove markdown code block markup but keep the text content
pageSourceFileContent = pageSourceFileContent.replace(/```([^`]+)```/gm, ' $1 ')
// remove markdown table header markup but keep the text content
pageSourceFileContent = pageSourceFileContent.replace(/\|:?-+:?\|/gm, '')
// remove markdown table cell markup but keep the text content
pageSourceFileContent = pageSourceFileContent.replace(/\|([^|]+)\|/gm, ' $1 ')
// remove specific html tags completely
const tags = ['']
tags.forEach((tag) => {
pageSourceFileContent = pageSourceFileContent.replace(new RegExp(`<${tag}[^>]*>[\\s\\S]*?<\\/${tag}>`, 'g'), '')
})
// remove specific html tags but keep the text content
const tagsToKeepContent = ['u', 'Containers', 'img', 'a']
tagsToKeepContent.forEach((tag) => {
pageSourceFileContent = pageSourceFileContent.replace(new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, 'g'), ' $1 ')
})
// remove all new lines (either \r, \n)
pageSourceFileContent = pageSourceFileContent.replace(/[\r|\n]/gm, '')
// calculate the first 200 characters of the page content
let pageContent = pageSourceFileContent.slice(0, 200)
// trim space
pageContent = pageContent.trim()
// if pageSourceFileContent is longer than 200 characters, add ellipsis
if (pageSourceFileContent.length > 100)
pageContent += '...'
if (context.pageData.frontmatter?.layout === 'home') {
pageContent = context.pageData.frontmatter?.hero?.tagline ?? siteDescription
}
head.push([
'meta',
{ name: 'description', content: pageContent },
])
head.push([
'meta',
{ property: 'og:title', content: context.title },
])
head.push([
'meta',
{ property: 'og:description', content: pageContent },
])
head.push([
'meta',
{ property: 'og:title', content: context.title },
])
head.push([
'meta',
{ property: 'twitter:description', content: pageContent },
])
return head
},
});
export default withThemeContext(themeConfig, genConfig)

View File

@ -1,11 +1,13 @@
declare module 'markdown-it-pangu' {
import { PluginSimple } from 'markdown-it'
import type { PluginSimple } from 'markdown-it'
const pangu: PluginSimple
export default pangu
}
declare module 'markdown-it-katex' {
import { PluginSimple } from 'markdown-it'
import type { PluginSimple } from 'markdown-it'
const katex: PluginSimple
export default katex
}

View File

@ -1,6 +0,0 @@
/** Repo */
export const githubRepoLink = 'https://github.com/project-trans/RLE-wiki'
/** vitepress 根目录 */
export const rootDir = 'docs'
/** 文档所在目录 */
export const include = ['campus', 'contributor-guide', 'fashion']

View File

@ -1,41 +0,0 @@
import type { DefaultTheme } from 'vitepress'
type NavConfig = DefaultTheme.Config['nav']
const nav: NavConfig = [
{
text: '大学指南',
link: '/campus/',
},
{
text: '时尚护理',
link: '/fashion/',
},
{
text: '安全防护',
link: '/personal-safety/',
},
{
text: '其它',
link: '/others/',
},
{
text: '贡献指南',
items: [
{
text: '校园版块投稿指南',
link: '/contributor-guide/campus.md',
},
{
text: '其他投稿指南',
link: '/contributor-guide/other.md',
},
{
text: '校园版块贡献模板',
link: '/contributor-guide/CampusTemplate.md',
},
],
},
]
export default nav

View File

@ -1,60 +0,0 @@
import { ContentData, createContentLoader, defineLoader } from "vitepress";
let 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;
insert(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>>> {
let raw: ContentData[] = await contentLoader.load()
let 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: value, children: {} }
} else {
node.children[path[0]].value = value
}
} else {
if (!(path[0] in node.children)) {
let 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 (let v of raw) {
let frontmatter = v.frontmatter ?? null
let 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: copyright
})
}
}
return trie;
}
})

View File

@ -1,75 +0,0 @@
import type {
SidebarItem,
SidebarMultiItem,
} from 'vitepress-sidebar'
import { generateSidebar } from 'vitepress-sidebar'
import type Options from 'vitepress-sidebar'
export const sidebar = generate()
function generate() {
const baseConfig = {
useTitleFromFrontmatter: true,
useFolderTitleFromIndexFile: true,
useFolderLinkFromIndexFile: true,
excludeFilesByFrontmatter: true,
collapsed: true,
} satisfies Partial<Options>
const sidebar = generateSidebar([
// 大学指南
{
...baseConfig,
documentRootPath: '/docs',
scanStartPath: 'campus',
resolvePath: '/campus/',
},
// 贡献指南
{
...baseConfig,
documentRootPath: '/docs',
scanStartPath: 'contributor-guide',
resolvePath: '/contributor-guide/',
},
// 时尚护理
{
...baseConfig,
documentRootPath: '/docs',
scanStartPath: 'fashion',
resolvePath: '/fashion/',
},
// 安全防护
{
...baseConfig,
documentRootPath: '/docs',
scanStartPath: 'personal-safety',
resolvePath: '/personal-safety/',
},
// 其它
{
...baseConfig,
documentRootPath: '/docs',
scanStartPath: 'others',
resolvePath: '/others/',
},
])
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,63 +1,9 @@
// https://vitepress.dev/guide/custom-theme
import type { Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import { h } from 'vue'
import {
NolebaseEnhancedReadabilitiesMenu,
NolebaseEnhancedReadabilitiesPlugin,
NolebaseEnhancedReadabilitiesScreenMenu,
} from '@nolebase/vitepress-plugin-enhanced-readabilities/client'
import {
NolebaseHighlightTargetedHeading,
} from '@nolebase/vitepress-plugin-highlight-targeted-heading/client'
import {
NolebaseGitChangelogPlugin,
} from '@nolebase/vitepress-plugin-git-changelog/client'
import AppearanceToggle from './components/AppearanceToggle.vue'
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 PtjsTheme from '@project-trans/vitepress-theme-project-trans/theme'
import 'uno.css'
import AppFooter from './components/AppFooter.vue'
import './style.css'
export default {
extends: DefaultTheme,
Layout: () => {
return h(AppearanceToggle, null, {
default: () => h(DefaultTheme.Layout, null, {
// https://vitepress.dev/guide/extending-default-theme#layout-slots
'doc-top': () => [
h(NolebaseHighlightTargetedHeading),
],
'doc-after': () => [h(AppFooter)],
'nav-bar-content-after': () => [
h(NolebaseEnhancedReadabilitiesMenu),
],
'nav-screen-content-after': () => [
h(NolebaseEnhancedReadabilitiesScreenMenu),
],
}),
})
},
enhanceApp({ app }) {
app.use(NolebaseEnhancedReadabilitiesPlugin, {
spotlight: {
defaultToggle: true,
},
})
app.use(NolebaseGitChangelogPlugin, {
locales: {
'zh-CN': {
lastEditedDateFnsLocaleName: 'zhCN',
},
},
})
},
} satisfies Theme
extends: PtjsTheme,
}

View File

@ -25,7 +25,7 @@ hero:
---
<script setup>
import HomeContent from './.vitepress/theme/components/HomeContent.vue'
import HomeContent from '@project-trans/vitepress-theme-project-trans/components/HomeContent.vue'
</script>
<HomeContent>

View File

@ -1,14 +1,4 @@
import { resolve } from 'node:path'
import { defineConfig } from 'vite'
import Components from 'unplugin-vue-components/vite'
import UnoCSS from 'unocss/vite'
import { GitChangelog, GitChangelogMarkdownSection } from '@nolebase/vitepress-plugin-git-changelog/vite'
import {
MarkdownSectionWrapper,
PageHeaderTemplate,
TemplateAppSBox,
TemplateCopyrightInfo,
} from './.vitepress/plugins/MarkdownSectionWrapper'
export default defineConfig({
server: {
@ -22,55 +12,4 @@ export default defineConfig({
},
},
},
plugins: [
MarkdownSectionWrapper(
[PageHeaderTemplate, TemplateCopyrightInfo],
[],
{
excludes: [],
exclude: (_, { helpers }): boolean => {
if (helpers.idEquals('index.md'))
return true
return false
},
},
),
GitChangelog({
repoURL: 'https://github.com/project-trans/RLE-wiki',
maxGitLogCount: 1000,
rewritePaths: {
'docs/': '',
},
}),
// GitChangelogMarkdownSection({
// sections: {
// disableChangelog: false,
// disableContributors: true,
// },
// getChangelogTitle: (): string => {
// return '文件历史'
// },
// excludes: [],
// exclude: (_, { helpers }): boolean => {
// if (helpers.idEquals('index.md'))
// return true
// return false
// },
// }),
Components({
dirs: resolve(__dirname, '.vitepress/theme/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',
],
},
})

View File

@ -2,6 +2,7 @@
"name": "rle-wiki",
"type": "module",
"version": "1.0.0",
"packageManager": "pnpm@9.2.0",
"description": "RLE 指北",
"license": "CC-BY-SA-4.0",
"scripts": {
@ -9,33 +10,29 @@
"dev:wrangler": "wrangler pages dev ./docs/.vitepress/dist/",
"build": "vitepress build docs",
"preview": "vitepress preview docs",
"update-package": "pnpm dlx vp-update"
"update-package": "pnpm dlx vp-update",
"postinstall": "pnpm --filter @project-trans/* build"
},
"devDependencies": {
"@antfu/eslint-config": "^2.13.3",
"@cloudflare/workers-types": "^4.20240405.0",
"@iconify-json/octicon": "^1.1.53",
"@nolebase/vitepress-plugin-enhanced-readabilities": "2.0.0-rc3",
"@nolebase/vitepress-plugin-git-changelog": "2.0.0-rc3",
"@nolebase/vitepress-plugin-highlight-targeted-heading": "2.0.0-rc3",
"@antfu/eslint-config": "^2.21.1",
"@cloudflare/workers-types": "^4.20240620.0",
"@iconify-json/octicon": "^1.1.55",
"@project-trans/suggestion-box": "^0.0.9",
"@types/markdown-it": "^13.0.7",
"@project-trans/vitepress-theme-project-trans": "workspace:*",
"@types/markdown-it": "^13.0.8",
"@types/markdown-it-footnote": "^3.0.4",
"@unocss/eslint-plugin": "^0.59.0",
"@unocss/eslint-plugin": "^0.59.4",
"eslint": "^8.57.0",
"eslint-plugin-format": "^0.1.0",
"markdown-it-footnote": "^3.0.3",
"markdown-it-katex": "^2.0.3",
"markdown-it-pangu": "^1.0.2",
"md5": "^2.3.0",
"simple-git": "^3.24.0",
"eslint-plugin-format": "^0.1.2",
"simple-git": "^3.25.0",
"unbuild": "^2.0.0",
"unocss": "^0.58.9",
"unplugin-vue-components": "^0.26.0",
"vite": "^5.2.8",
"vitepress": "1.0.0-rc.42",
"vitepress-sidebar": "^1.22.0",
"vue": "^3.4.21",
"wrangler": "^3.49.0"
"vite": "^5.3.1",
"vitepress": "^1.2.3",
"vitepress-sidebar": "^1.23.2",
"vue": "^3.4.30",
"wrangler": "^3.61.0"
},
"pnpm": {
"overrides": {

View File

@ -0,0 +1,33 @@
import { defineBuildConfig } from 'unbuild'
export default defineBuildConfig({
entries: [
{
builder: 'mkdist',
input: './src',
outDir: './dist',
pattern: ['**/*.ts'],
format: 'esm',
loaders: ['js'],
},
{
builder: 'mkdist',
input: './src',
outDir: './dist',
pattern: ['**/*.ts'],
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

@ -0,0 +1,67 @@
{
"name": "@project-trans/vitepress-theme-project-trans",
"type": "module",
"version": "1.0.0",
"description": "RLE 指北",
"license": "MIT",
"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"
},
"./plugins/MarkdownSectionWrapper": {
"types": "./dist/plugins/MarkdownSectionWrapper.d.ts",
"import": "./dist/plugins/MarkdownSectionWrapper.mjs",
"require": "./dist/plugins/MarkdownSectionWrapper.js"
},
"./components/*": "./dist/components/*"
},
"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",
"@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

@ -8,9 +8,6 @@ import { NolebaseGitChangelog } from '@nolebase/vitepress-plugin-git-changelog/c
意见反馈
</h2>
<AppSBox />
<h2 id="文件历史">
文件历史
</h2>
<NolebaseGitChangelog />
</div>
</template>

View File

@ -1,11 +1,11 @@
<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>
<script setup lang="ts">
import SuggestionBox from '@project-trans/suggestion-box';
import '@project-trans/suggestion-box/dist/style.css'
</script>

View File

@ -1,16 +1,17 @@
<script setup lang="ts">
import { useData } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import { nextTick, provide, onMounted } from 'vue'
import { nextTick, provide } from 'vue'
const { isDark } = useData()
const isSSR = typeof window === 'undefined'
const enableTransitions = () => isSSR ? false :
'startViewTransition' in document &&
window.matchMedia('(prefers-reduced-motion: no-preference)').matches
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()) {
@ -18,16 +19,15 @@ provide('toggle-appearance', async ({ clientX: x, clientY: y }: MouseEvent) => {
return
}
if (!document.documentElement.classList.contains('VPSwitchAppearance-ViewTransition')) {
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)`
Math.max(y, innerHeight - y),
)}px at ${x}px ${y}px)`,
]
await (document as any).startViewTransition(async () => {
@ -40,8 +40,8 @@ provide('toggle-appearance', async ({ clientX: x, clientY: y }: MouseEvent) => {
{
duration: 500,
easing: 'ease-in',
pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)`
}
pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)`,
},
)
})
</script>

View File

@ -1,48 +1,51 @@
<script setup lang="ts">
import { useData } from "vitepress";
import { Node, Trie, data } from "../../plugins/CopyrightLoader.data";
import { useData } from 'vitepress'
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
node: Node<Record<string, any>> = that.root,
): Record<string, any> | null {
if (path.length === 0) {
return node.value;
}
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;
node.children[path[0]],
)
if (value === null)
value = node.value
return value
}
return node.value;
return node.value
}
const paths = useData()
.page.value.relativePath.replace('.md', '').split('/')
.filter((item: string) => item !== '');
const attrs = searchClosestInTrie(data, paths);
const frontmatter = useData().frontmatter.value;
.filter((item: string) => item !== '')
const attrs = searchClosestInTrie(data, paths)
const frontmatter = useData().frontmatter.value
const originUrlExists = (attrs?.copyright?.url ?? null) != null;
const originUrl = attrs?.copyright?.url ?? 'javascript:void(0)';
const originUrlExists = (attrs?.copyright?.url ?? null) != null
const originUrl = attrs?.copyright?.url ?? 'javascript:void(0)'
const license = attrs?.copyright?.license ?? null;
const licenseExists = license != null;
const licenseUrlExists = (attrs?.copyright?.licenseUrl ?? null) != null;
const license = attrs?.copyright?.license ?? null
const licenseExists = license != null
const licenseUrlExists = (attrs?.copyright?.licenseUrl ?? null) != null
const licenseUrl = attrs?.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 class="custom-block-title">
Copyright
</p>
<p>
<span>这篇文章 </span>
<a v-if="originUrlExists" :href="originUrl">{{ frontmatter.title }}</a>
@ -59,6 +62,6 @@ const licenseUrl = attrs?.copyright?.licenseUrl ?? 'javascript:void(0)'
<span></span>
</p>
</div>
<hr />
<hr>
</div>
</template>

View File

@ -1,11 +1,11 @@
<script setup lang="ts">
import { useData } from 'vitepress'
import { computed, onMounted, ref, watchEffect } from 'vue';
import { computed, onMounted, ref, watchEffect } from 'vue'
const { frontmatter, page, theme, lang } = useData()
const { frontmatter, page, theme, lang } = useData()
const date = computed(
() => new Date(frontmatter.value.lastUpdated ?? page.value.lastUpdated)
() => new Date(frontmatter.value.lastUpdated ?? page.value.lastUpdated),
)
const isoDatetime = computed(() => date.value.toISOString())
@ -17,8 +17,8 @@ onMounted(() => {
theme.value.lastUpdated?.formatOptions?.forceLocale ? lang.value : undefined,
theme.value.lastUpdated?.formatOptions ?? {
dateStyle: 'short',
timeStyle: 'short'
}
timeStyle: 'short',
},
).format(date.value)
})
})
@ -33,16 +33,15 @@ const authors = computed(() => {
return [...author, '匿名']
})
</script>
<template>
<div class="flex flex-wrap gap-4 mt-4 mb-10">
<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 class="space-x-2">
<span v-for="author of authors">
<span v-for="(author, index) in authors" :key="index">
{{ author }}
</span>
</span>

View File

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

View File

@ -0,0 +1,325 @@
import { readFileSync, statSync } from 'node:fs'
import { dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { GitChangelog } from '@nolebase/vitepress-plugin-git-changelog/vite'
import {
MarkdownSectionWrapper,
PageHeaderTemplate,
TemplateCopyrightInfo,
} from '@project-trans/vitepress-theme-project-trans/plugins/MarkdownSectionWrapper'
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'
// https://vitepress.dev/reference/site-config
function genConfig() {
const themeConfig = useThemeContext()
const { siteTitle, siteDescription, githubRepoLink, rootDir, 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: '清除查询条件',
footer: {
selectText: '选择',
navigateText: '切换',
},
},
},
},
},
// 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: (context) => {
const head = [...context.head] || []
const pageSourceFilePath = join(rootDir, context.pageData.filePath)
const pageSourceFileStat = statSync(
join(rootDir, context.pageData.filePath),
)
if (pageSourceFileStat.isDirectory()) {
head.push([
'meta',
{
property: 'og:title',
content: siteTitle,
},
])
head.push([
'meta',
{
name: 'description',
content: siteDescription,
},
])
return head
}
let pageSourceFileContent = readFileSync(pageSourceFilePath, {
encoding: 'utf-8',
})
// remove all frontmatter
pageSourceFileContent = pageSourceFileContent.replace(
/---[\s\S]*?---/,
'',
)
// remove markdown heading markup but keep the text content
pageSourceFileContent = pageSourceFileContent.replace(
/^(#+)\s+(.*)/gm,
' $2 ',
)
// remove markdown link markup but keep the text content
pageSourceFileContent = pageSourceFileContent.replace(
/\[([^\]]+)\]\([^)]+\)/gm,
' $1 ',
)
// remove markdown image markup but keep the text content
pageSourceFileContent = pageSourceFileContent.replace(
/\!\[([^\]]+)\]\([^)]+\)/gm,
' $1 ',
)
// remove markdown reference link markup but keep the text content
pageSourceFileContent = pageSourceFileContent.replace(/\[.*]/gm, '')
// remove markdown bold markup but keep the text content
pageSourceFileContent = pageSourceFileContent.replace(
/\*\*([^*]+)\*\*/gm,
' $1 ',
)
pageSourceFileContent = pageSourceFileContent.replace(
/__([^*]+)__/gm,
' $1 ',
)
// remove markdown italic markup but keep the text content
pageSourceFileContent = pageSourceFileContent.replace(
/\*([^*]+)\*/gm,
' $1 ',
)
pageSourceFileContent = pageSourceFileContent.replace(
/_([^*]+)_/gm,
' $1 ',
)
// remove markdown code markup but keep the text content
pageSourceFileContent = pageSourceFileContent.replace(
/`([^`]+)`/gm,
' $1 ',
)
// remove markdown code block markup but keep the text content
pageSourceFileContent = pageSourceFileContent.replace(
/```([^`]+)```/gm,
' $1 ',
)
// remove markdown table header markup but keep the text content
pageSourceFileContent = pageSourceFileContent.replace(/\|:?-+:?\|/gm, '')
// remove markdown table cell markup but keep the text content
pageSourceFileContent = pageSourceFileContent.replace(
/\|([^|]+)\|/gm,
' $1 ',
)
// remove specific html tags completely
const tags = ['']
tags.forEach((tag) => {
pageSourceFileContent = pageSourceFileContent.replace(
new RegExp(`<${tag}[^>]*>[\\s\\S]*?<\\/${tag}>`, 'g'),
'',
)
})
// remove specific html tags but keep the text content
const tagsToKeepContent = ['u', 'Containers', 'img', 'a']
tagsToKeepContent.forEach((tag) => {
pageSourceFileContent = pageSourceFileContent.replace(
new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, 'g'),
' $1 ',
)
})
// remove all new lines (either \r, \n)
pageSourceFileContent = pageSourceFileContent.replace(/[\r|\n]/gm, '')
// calculate the first 200 characters of the page content
let pageContent = pageSourceFileContent.slice(0, 200)
// trim space
pageContent = pageContent.trim()
// if pageSourceFileContent is longer than 200 characters, add ellipsis
if (pageSourceFileContent.length > 100)
pageContent += '...'
if (context.pageData.frontmatter?.layout === 'home') {
pageContent
= context.pageData.frontmatter?.hero?.tagline ?? siteDescription
}
head.push(['meta', { name: 'description', content: pageContent }])
head.push(['meta', { property: 'og:title', content: context.title }])
head.push(['meta', { property: 'og:description', content: pageContent }])
head.push(['meta', { property: 'og:title', content: context.title }])
head.push([
'meta',
{ property: 'twitter:description', content: pageContent },
])
return head
},
vite: {
plugins: [
MarkdownSectionWrapper(
[PageHeaderTemplate, TemplateCopyrightInfo],
[],
{
excludes: [],
exclude: (_, { helpers }): boolean => {
if (helpers.idEquals('index.md'))
return true
return false
},
},
),
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',
],
},
},
})
}
export default genConfig

View File

@ -0,0 +1,62 @@
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,11 +1,12 @@
import { resolve, relative } from 'node:path'
import { dirname, relative, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import type { Plugin } from 'vite'
import {
pathEndsWith,
pathEquals,
pathStartsWith,
} from '../path'
} from '../utils/path'
interface Context {
helpers: {
@ -79,7 +80,7 @@ export interface MarkdownSectionWrapperOptions {
exclude?: (id: string, context: Context) => boolean
}
export function MarkdownSectionWrapper(headerTransformers: ((frontmatter: string|null, text: string, id: string) => string)[], footerTransformers: ((frontmatter: string|null, text: string, id: string) => string)[], options?: MarkdownSectionWrapperOptions): Plugin {
export function MarkdownSectionWrapper(headerTransformers: ((frontmatter: string | null, text: string, id: string) => string)[], footerTransformers: ((frontmatter: string | null, text: string, id: string) => string)[], options?: MarkdownSectionWrapperOptions): Plugin {
const {
excludes = ['index.md'],
exclude = () => false,
@ -116,18 +117,18 @@ export function MarkdownSectionWrapper(headerTransformers: ((frontmatter: string
if (exclude(id, { helpers: { idEndsWith, idEquals, idStartsWith, pathEndsWith, pathEquals, pathStartsWith } }))
return null
let frontmatter = (code.match(/(^---$(\s|\S)+?^---$)/m)?.[0] ?? null)
let text = code.replace(/(^---$(\s|\S)+?^---$)/m, '')
const frontmatter = (code.match(/(^---$([\s\S])+?^---$)/m)?.[0] ?? null)
const text = code.replace(/(^---$([\s\S])+?^---$)/m, '')
let headers: string[] = headerTransformers.map(f => f(frontmatter, text, id))
let footers: string[] = footerTransformers.map(f => f(frontmatter, text, id))
const headers: string[] = headerTransformers.map(f => f(frontmatter, text, id))
const footers: string[] = footerTransformers.map(f => f(frontmatter, text, id))
return [frontmatter, ...headers, text, ...footers].join("")
return [frontmatter, ...headers, text, ...footers].join('')
},
}
}
export function TemplateAppSBox(_frontmatter: string|null, _text: string, _id: string): string {
export function TemplateAppSBox(_frontmatter: string | null, _text: string, _id: string): string {
return `
##
@ -137,7 +138,7 @@ export function TemplateAppSBox(_frontmatter: string|null, _text: string, _id: s
`
}
export function TemplateCopyrightInfo(_frontmatter: string|null, _text: string, _id: string): string {
export function TemplateCopyrightInfo(_frontmatter: string | null, _text: string, _id: string): string {
return `
<CopyrightInfo />
@ -145,21 +146,23 @@ export function TemplateCopyrightInfo(_frontmatter: string|null, _text: string,
`
}
const __dirname = dirname(fileURLToPath(import.meta.url))
const ROOT = resolve(__dirname, '../../')
export function PageHeaderTemplate(_frontmatter: string|null, _text: string, id: string): string {
export function PageHeaderTemplate(_frontmatter: string | null, _text: string, id: string): string {
if (!id.endsWith('.md'))
return ''
id = relative(ROOT, id)
if (id == 'index.md')
if (id === 'index.md')
return ''
return `
# {{ $frontmatter.title }}
<PageInfo />
`}
`
}

View File

@ -0,0 +1,29 @@
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

@ -0,0 +1,64 @@
// https://vitepress.dev/guide/custom-theme
import type { Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import { h } from 'vue'
import {
NolebaseEnhancedReadabilitiesMenu,
NolebaseEnhancedReadabilitiesPlugin,
NolebaseEnhancedReadabilitiesScreenMenu,
} from '@nolebase/vitepress-plugin-enhanced-readabilities/client'
import {
NolebaseHighlightTargetedHeading,
} from '@nolebase/vitepress-plugin-highlight-targeted-heading/client'
import {
NolebaseGitChangelogPlugin,
} from '@nolebase/vitepress-plugin-git-changelog/client'
import type Options from 'vitepress-sidebar'
import AppearanceToggle from './components/AppearanceToggle.vue'
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 AppFooter from './components/AppFooter.vue'
export type SidebarOptions = Options
export default {
extends: DefaultTheme,
Layout: () => {
return h(AppearanceToggle, null, {
default: () => h(DefaultTheme.Layout, null, {
// https://vitepress.dev/guide/extending-default-theme#layout-slots
'doc-top': () => [
h(NolebaseHighlightTargetedHeading),
],
'doc-after': () => [h(AppFooter)],
'nav-bar-content-after': () => [
h(NolebaseEnhancedReadabilitiesMenu),
],
'nav-screen-content-after': () => [
h(NolebaseEnhancedReadabilitiesScreenMenu),
],
}),
})
},
enhanceApp({ app }) {
app.use(NolebaseEnhancedReadabilitiesPlugin, {
spotlight: {
defaultToggle: true,
},
})
app.use(NolebaseGitChangelogPlugin, {
locales: {
'zh-CN': {
lastEditedDateFnsLocaleName: 'zhCN',
},
},
})
},
} satisfies Theme

View File

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

View File

@ -0,0 +1,26 @@
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,2 +1,3 @@
packages:
- functions
- packages/*