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
parent
25eaa2ba69
commit
d31461684e
|
@ -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']
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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']
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
})
|
|
@ -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,
|
||||
})
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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',
|
||||
],
|
||||
},
|
||||
})
|
||||
|
|
37
package.json
37
package.json
|
@ -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": {
|
||||
|
|
|
@ -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 },
|
||||
})
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -8,9 +8,6 @@ import { NolebaseGitChangelog } from '@nolebase/vitepress-plugin-git-changelog/c
|
|||
意见反馈
|
||||
</h2>
|
||||
<AppSBox />
|
||||
<h2 id="文件历史">
|
||||
文件历史
|
||||
</h2>
|
||||
<NolebaseGitChangelog />
|
||||
</div>
|
||||
</template>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
},
|
||||
})
|
|
@ -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 />
|
||||
|
||||
`}
|
||||
`
|
||||
}
|
|
@ -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,
|
||||
})
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
export * from './path'
|
||||
export * from './themeContext'
|
|
@ -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()!
|
||||
}
|
9745
pnpm-lock.yaml
9745
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -1,2 +1,3 @@
|
|||
packages:
|
||||
- functions
|
||||
- packages/*
|
||||
|
|
Loading…
Reference in New Issue