chore: migrate to dedicated git changelog plugin (#250)

Signed-off-by: Neko Ayaka <neko@ayaka.moe>
Co-authored-by: Rizumu Ayaka <rizumu@ayaka.moe>
pull/286/head
Neko Ayaka 2024-02-07 14:50:24 +08:00 committed by Rizumu Ayaka
parent 334d942de5
commit c88c4e5636
16 changed files with 82 additions and 422 deletions

View File

@ -9,10 +9,11 @@ declare module 'vue' {
export interface GlobalComponents {
AppearanceToggle: typeof import('./theme/components/AppearanceToggle.vue')['default']
AppSBox: typeof import('./theme/components/AppSBox.vue')['default']
Changelog: typeof import('./theme/components/Changelog.vue')['default']
HomeContent: typeof import('./theme/components/HomeContent.vue')['default']
PageInfo: typeof import('./theme/components/PageInfo.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Starport: typeof import('vue-starport')['Starport']
StarportCarrier: typeof import('vue-starport')['StarportCarrier']
}
}

View File

@ -2,4 +2,10 @@ declare module 'markdown-it-pangu' {
import { PluginSimple } from 'markdown-it'
const pangu: PluginSimple
export default pangu
}
}
declare module 'markdown-it-katex' {
import { PluginSimple } from 'markdown-it'
const katex: PluginSimple
export default katex
}

View File

@ -1,92 +0,0 @@
import type { Plugin } from 'vite'
import md5 from 'md5'
import simpleGit from 'simple-git'
import { type SimpleGit } from 'simple-git'
import type { CommitInfo, ContributorInfo } from '../types'
import { include, rootDir } from '../meta'
let changeLogs: CommitInfo[] | undefined
let git: SimpleGit
const ID = '/virtual-changelog'
export function ChangeLog({
maxGitLogCount = 200
} = {}): Plugin {
return {
name: 'git-changelog',
resolveId(id) {
return id === ID ? ID : null
},
load(id) {
if (id !== ID)
return null
return `export default ${JSON.stringify(changeLogs)}`
},
async buildStart(options) {
if (changeLogs) return
git ??= simpleGit({
maxConcurrentProcesses: 200,
})
// 设置 git 正常展示中文路径
await git.raw(['config', '--global', 'core.quotepath', 'false'])
const logs = (await git.log({ maxCount: maxGitLogCount })).all as CommitInfo[]
for (const log of logs) {
/** 发版日志 */
if (log.message.includes('release: ')) {
log.version = log.message.split(' ')[1].trim()
continue
}
/** 文档日志 */
// const raw = await git.raw(['diff-tree', '--no-commit-id', '--name-only', '-r', log.hash])
const raw = await git.raw(['diff-tree', '--no-commit-id', '--name-status', '-r', '-M', log.hash])
delete log.body
const files = raw.replace(/\\/g, '/').trim().split('\n').map(str => str.split('\t'))
log.path = Array.from(new Set(
files
.filter(i => !!i[1]?.match(RegExp(`^${rootDir ? rootDir + '\\/' : ''}(${include.join('|')})\\/.+\\.md$`))?.[0]),
))
log.authorAvatar = md5(log.author_email) as string
}
const result = logs.filter(i => i.path?.length || i.version)
changeLogs = result
}
}
}
export async function getContributorsAt(path: string) {
try {
const list = (await git.raw(['log', '--pretty=format:"%an|%ae"', '--', path]))
.split('\n')
.map(i => i.slice(1, -1).split('|') as [string, string])
const map: Record<string, ContributorInfo> = {}
list
.filter(i => i[1])
.forEach((i) => {
if (!map[i[1]]) {
map[i[1]] = {
name: i[0],
count: 0,
hash: md5(i[1]) as string,
}
}
map[i[1]].count++
})
return Object.values(map).sort((a, b) => b.count - a.count)
}
catch (e) {
console.error(e)
return []
}
}

View File

@ -17,7 +17,6 @@ export function MarkdownTransform(): Plugin {
return null
code = pageHeaderTemplate(code)
code = pageFooterTemplate(code)
return code
},
@ -32,10 +31,3 @@ const pageHeaderTemplate = (code: string) => !code.startsWith('---') ? code : co
<PageInfo />
`)
const pageFooterTemplate = (code: string) => `${code}
##
<Changelog />
`

View File

@ -1,95 +0,0 @@
<script setup lang="ts">
// @ts-expect-error virtual
import changelog from '/virtual-changelog'
import { CommitInfo } from '../../types'
import { renderCommitMessage } from '../../utils'
import { githubRepoLink } from "../../meta"
import { useRawPath } from '../composables/route'
import { useCommits } from '../composables/changelog'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import 'dayjs/locale/zh-cn'
import { computed } from 'vue'
dayjs.locale('zh-cn')
dayjs.extend(relativeTime)
const rawPath = useRawPath()
const allCommits = changelog as CommitInfo[]
const commits = useCommits(allCommits, rawPath)
const lastChangeDate = computed(() => {
const date: string = commits.value[0]?.date || ''
if (!date) return null
return dayjs(date)
})
const isFreshChange = computed(() => {
if (!lastChangeDate.value) return false
return lastChangeDate.value.isAfter(dayjs().subtract(1, 'day'))
})
</script>
<template>
<em v-if="!commits.length" opacity="70"></em>
<details v-else class="details custom-block [&_svg]:open:-rotate-180" :class="isFreshChange && '!bg-green/16'">
<summary style="list-style: none" class="flex justify-between items-center select-none hover:text-$vp-c-brand-1">
<span class="inline-flex items-center gap-3 text-$vp-custom-block-details-text">
<span class="i-octicon:history-16" />
<span v-if="commits[0]">
此文档最后编辑于 {{ lastChangeDate?.fromNow() }}
</span>
</span>
<span class="inline-flex items-center gap-3 !font-400">
<span>
查看全部
</span>
<svg class="i-octicon:chevron-down-16" />
</span>
</summary>
<div class="my-2 grid grid-cols-[30px_auto] -ml-1.5 gap-1.5 children:my-auto">
<template v-for="(commit, idx) of commits" :key="commit.hash">
<!-- <template v-if="idx === 0 && !commit.version">
<div m="t-1" />
<div m="t-1" />
<div class="m-auto inline-flex bg-gray-400/10 w-7 h-7 rounded-full text-sm opacity-90">
<div class="i-octicon:git-pull-request-draft-16" m="auto" />
</div>
<div>
<code>Pending for release...</code>
</div>
</template> -->
<template v-if="commit.version">
<div m="t-1" />
<div m="t-1" />
<div class="m-auto inline-flex bg-gray-400/10 w-7 h-7 rounded-full text-sm opacity-90">
<div class="i-octicon:rocket-16" m="auto" />
</div>
<div>
<a :href="`${githubRepoLink}/releases/tag/${commit.version}`" target="_blank">
<code class="!text-primary font-bold">{{ commit.version }}</code>
</a>
<span class="opacity-50 text-xs"> on {{ new Date(commit.date).toLocaleDateString() }}</span>
</div>
</template>
<template v-else>
<div class="i-octicon:git-commit-16 m-auto transform rotate-90 opacity-30" />
<div>
<a :href="`${githubRepoLink}/commit/${commit.hash}`" target="_blank">
<code class="!text-xs !text-$vt-c-text-2 !hover:text-primary">{{ commit.hash.slice(0, 5) }}</code>
</a>
<span text="sm">
-
<span v-html="renderCommitMessage(commit.message)" />
</span>
<span class="opacity-50 text-xs"> on {{ new Date(commit.date).toLocaleDateString() }}</span>
</div>
</template>
</template>
</div>
</details>
</template>

View File

@ -1,23 +0,0 @@
import { CommitInfo } from "../../types"
import { type MaybeRefOrGetter, computed, toValue } from 'vue'
export function useCommits(allCommits: CommitInfo[], path: MaybeRefOrGetter<string>) {
return computed<CommitInfo[]>(() => {
let currentPath = toValue(path)
const commits = allCommits.filter(c => {
return c.version || c.path?.find(p => {
const action = p[0], path1 = p[1]?.toLowerCase(), path2 = p[2]?.toLowerCase()
const res = currentPath === path1 || currentPath === path2
if (res && action.startsWith('R')) currentPath = path1
return res
})
})
return commits.filter((i, idx) => {
if (i.version && (!commits[idx + 1] || commits[idx + 1]?.version))
return false
return true
})
})
}

View File

@ -1,26 +0,0 @@
import { useRoute } from 'vitepress'
import { computed } from 'vue'
import { rootDir } from '../../meta'
export function useRawPath() {
const route = useRoute()
return computed(() => {
let path = decodeURIComponent(route.path).toLowerCase()
if (path.endsWith('/')) {
path += 'index.md'
} else {
path = path.replace(/^\/(.+?)(\.html)?$/s, '$1.md')
}
return pathJoin(rootDir, path).toLowerCase()
})
}
export function pathJoin(...args: string[]) {
return args.map((part, i) => {
if (i === 0) {
return part.trim().replace(/[\/]*$/g, '')
} else {
return part.trim().replace(/(^[\/]*|[\/]*$)/g, '')
}
}).filter(x=>x.length).join('/')
}

View File

@ -14,8 +14,13 @@ import {
NolebaseHighlightTargetedHeading,
} from '@nolebase/vitepress-plugin-highlight-targeted-heading'
import {
NolebaseGitChangelogPlugin
} from '@nolebase/vitepress-plugin-git-changelog/client'
import '@nolebase/vitepress-plugin-enhanced-readabilities/dist/style.css'
import '@nolebase/vitepress-plugin-highlight-targeted-heading/dist/style.css'
import '@nolebase/vitepress-plugin-git-changelog/client/style.css'
import './style.css'
import 'uno.css'
@ -40,5 +45,6 @@ export default {
},
enhanceApp({ app }) {
app.use(NolebaseEnhancedReadabilitiesPlugin)
app.use(NolebaseGitChangelogPlugin)
}
} satisfies Theme

View File

@ -1,18 +0,0 @@
export interface ContributorInfo {
name: string
count: number
hash: string
}
export interface CommitInfo {
path: string[][]
version?: string
hash: string
date: string
message: string
refs?: string
body?: string
author_name: string
author_email: string
authorAvatar: string
}

View File

@ -1,22 +0,0 @@
import { githubRepoLink } from "./meta"
export function renderMarkdown(markdownText = '') {
const htmlText = markdownText
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
.replace(/^\> (.*$)/gim, '<blockquote>$1</blockquote>')
.replace(/\*\*(.*)\*\*/gim, '<b>$1</b>')
.replace(/\*(.*)\*/gim, '<i>$1</i>')
.replace(/!\[(.*?)\]\((.*?)\)/gim, '<img alt=\'$1\' src=\'$2\' />')
.replace(/\[(.*?)\]\((.*?)\)/gim, '<a href=\'$2\'>$1</a>')
.replace(/`(.*?)`/gim, '<code>$1</code>')
.replace(/\n$/gim, '<br />')
return htmlText.trim()
}
export function renderCommitMessage(msg: string) {
return renderMarkdown(msg)
.replace(/\#([0-9]+)/g, `<a href=\'${githubRepoLink}/issues/$1\'>#$1</a>`)
}

View File

@ -1,49 +0,0 @@
---
outline: deep
---
# Runtime API Examples
This page demonstrates usage of some of the runtime APIs provided by VitePress.
The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files:
```md
<script setup>
import { useData } from 'vitepress'
const { theme, page, frontmatter } = useData()
</script>
## Results
### Theme Data
<pre>{{ theme }}</pre>
### Page Data
<pre>{{ page }}</pre>
### Page Frontmatter
<pre>{{ frontmatter }}</pre>
```
<script setup>
import { useData } from 'vitepress'
const { site, theme, page, frontmatter } = useData()
</script>
## Results
### Theme Data
<pre>{{ theme }}</pre>
### Page Data
<pre>{{ page }}</pre>
### Page Frontmatter
<pre>{{ frontmatter }}</pre>
## More
Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata).

View File

@ -1,85 +0,0 @@
# Markdown Extension Examples
This page demonstrates some of the built-in markdown extensions provided by VitePress.
## Syntax Highlighting
VitePress provides Syntax Highlighting powered by [Shikiji](https://github.com/antfu/shikiji), with additional features like line-highlighting:
**Input**
````md
```js{4}
export default {
data () {
return {
msg: 'Highlighted!'
}
}
}
```
````
**Output**
```js{4}
export default {
data () {
return {
msg: 'Highlighted!'
}
}
}
```
## Custom Containers
**Input**
```md
::: info
This is an info box.
:::
::: tip
This is a tip.
:::
::: warning
This is a warning.
:::
::: danger
This is a dangerous warning.
:::
::: details
This is a details block.
:::
```
**Output**
::: info
This is an info box.
:::
::: tip
This is a tip.
:::
::: warning
This is a warning.
:::
::: danger
This is a dangerous warning.
:::
::: details
This is a details block.
:::
## More
Check out the documentation for the [full list of markdown extensions](https://vitepress.dev/guide/markdown).

View File

@ -3,12 +3,34 @@ import { defineConfig } from 'vite'
import { MarkdownTransform } from './.vitepress/plugins/markdownTransform'
import Components from 'unplugin-vue-components/vite'
import UnoCSS from 'unocss/vite'
import { ChangeLog } from './.vitepress/plugins/changelog'
import { GitChangelog, GitChangelogMarkdownSection } from '@nolebase/vitepress-plugin-git-changelog/vite'
export default defineConfig({
plugins: [
MarkdownTransform(),
ChangeLog(),
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$/],

View File

@ -16,6 +16,7 @@
"devDependencies": {
"@iconify-json/octicon": "^1.1.52",
"@nolebase/vitepress-plugin-enhanced-readabilities": "^1.9.0",
"@nolebase/vitepress-plugin-git-changelog": "^1.20.1",
"@nolebase/vitepress-plugin-highlight-targeted-heading": "^1.5.0",
"@types/markdown-it": "^13.0.7",
"@types/markdown-it-footnote": "^3.0.3",

View File

@ -22,6 +22,9 @@ devDependencies:
'@nolebase/vitepress-plugin-enhanced-readabilities':
specifier: ^1.9.0
version: 1.9.0(vitepress@1.0.0-rc.32)(vue@3.3.13)
'@nolebase/vitepress-plugin-git-changelog':
specifier: ^1.20.1
version: 1.20.1(dayjs@1.11.10)(md5@2.3.0)(simple-git@3.21.0)(vitepress@1.0.0-rc.32)(vue@3.3.13)
'@nolebase/vitepress-plugin-highlight-targeted-heading':
specifier: ^1.5.0
version: 1.5.0(vitepress@1.0.0-rc.32)(vue@3.3.13)
@ -2066,6 +2069,22 @@ packages:
vue: 3.3.13
dev: true
/@nolebase/vitepress-plugin-git-changelog@1.20.1(dayjs@1.11.10)(md5@2.3.0)(simple-git@3.21.0)(vitepress@1.0.0-rc.32)(vue@3.3.13):
resolution: {integrity: sha512-wtBhPWPPFa/hMqCG2SLPUUbCwFBkqC5U4VbsVc5kAN0RA/pM+zdjV79xZd+JepnKsNtwuISMFVsGqqybymmTAg==}
peerDependencies:
dayjs: '>=1.11.1'
md5: '>=2.3.0'
simple-git: '>=3.21.0'
vitepress: '>=1.0.0-rc.39'
vue: '>=3.3.0'
dependencies:
dayjs: 1.11.10
md5: 2.3.0
simple-git: 3.21.0
vitepress: 1.0.0-rc.32(@algolia/client-search@4.22.0)(postcss@8.4.32)(search-insights@2.13.0)
vue: 3.3.13
dev: true
/@nolebase/vitepress-plugin-highlight-targeted-heading@1.5.0(vitepress@1.0.0-rc.32)(vue@3.3.13):
resolution: {integrity: sha512-OnHLuwu//XBSDTgIlwWv2rvpNhBjc/89cTivAQ2Kr/SP+FJiSCLuYJnF/TGaqyrARajLDpqkwfC7sDJjcLo3LQ==}
peerDependencies:

23
tsconfig.json 100644
View File

@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"lib": [
"DOM",
"DOM.Iterable",
"ESNext"
],
"module": "ESNext", /* Specify what module code is generated. */
"moduleResolution": "Bundler",
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"strict": true, /* Enable all strict type-checking options. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": [
"docs/*.d.ts",
"docs/*.ts",
"docs/.vitepress/**/*.d.ts",
"docs/.vitepress/**/*.ts",
"docs/.vitepress/**/*.vue",
]
}