feat(next): add change log to the footer (#233)

pull/286/head
Rizumu Ayaka 2023-12-20 08:41:16 +08:00
parent 2547c0ba82
commit 26ff059973
13 changed files with 304 additions and 3 deletions

View File

@ -7,6 +7,7 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
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']

View File

@ -4,6 +4,7 @@ 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'
// https://vitepress.dev/reference/site-config
export default defineConfig({
@ -17,7 +18,7 @@ export default defineConfig({
md.use(katex);
},
},
dir: 'docs',
dir: rootDir,
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
siteTitle: 'RLE.wiki',
@ -25,11 +26,11 @@ export default defineConfig({
sidebar,
socialLinks: [
{ icon: 'github', link: 'https://github.com/project-trans/RLE-wiki' }
{ icon: 'github', link: githubRepoLink }
],
editLink: {
pattern: 'https://github.com/project-trans/RLE-wiki/edit/main/docs/:path',
pattern: `${githubRepoLink}/edit/main/docs/:path`,
text: '在 GitHub 上编辑此页面', // label localization
},

View File

@ -0,0 +1,6 @@
/** 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

@ -0,0 +1,92 @@
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: 'vueuse-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,6 +17,7 @@ export function MarkdownTransform(): Plugin {
return null
code = pageHeaderTemplate(code)
code = pageFooterTemplate(code)
return code
},
@ -31,3 +32,10 @@ const pageHeaderTemplate = (code: string) => code.replace(/(^---$(\s|\S)+^---$)/
<PageInfo />
`)
const pageFooterTemplate = (code: string) => `${code}
##
<Changelog />
`

View File

@ -0,0 +1,66 @@
<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'
const rawPath = useRawPath()
const allCommits = changelog as CommitInfo[]
const commits = useCommits(allCommits, rawPath)
console.log('changelog', changelog)
console.log('rawPath', rawPath.value)
console.log('allCommits', allCommits)
console.log('commits', commits)
</script>
<template>
<em v-if="!commits.length" opacity="70"></em>
<div class="grid grid-cols-[30px_auto] -ml-1 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>
</template>

View File

@ -0,0 +1,26 @@
import { rootDir } from "../../meta"
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)
currentPath = (rootDir ? rootDir + '/' : '') + currentPath
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

@ -0,0 +1,9 @@
import { useRoute } from 'vitepress'
import { computed } from 'vue'
export function useRawPath() {
const route = useRoute()
return computed(() => (
decodeURIComponent(route.path).replace(/^\/(.+)\.html$/, '$1.md').toLowerCase()
))
}

View File

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

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

@ -3,10 +3,12 @@ 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'
export default defineConfig({
plugins: [
MarkdownTransform(),
ChangeLog(),
Components({
dirs: resolve(__dirname, '.vitepress/theme/components'),
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],

View File

@ -22,6 +22,8 @@
"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.21.0",
"unocss": "^0.58.0",
"unplugin-vue-components": "^0.26.0",
"vite": "^5.0.5",

View File

@ -37,6 +37,12 @@ devDependencies:
markdown-it-pangu:
specifier: ^1.0.2
version: 1.0.2
md5:
specifier: ^2.3.0
version: 2.3.0
simple-git:
specifier: ^3.21.0
version: 3.21.0
unocss:
specifier: ^0.58.0
version: 0.58.0(postcss@8.4.32)(rollup@2.79.1)(vite@5.0.5)
@ -1836,6 +1842,18 @@ packages:
'@jridgewell/resolve-uri': 3.1.1
'@jridgewell/sourcemap-codec': 1.4.15
/@kwsites/file-exists@1.1.1:
resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==}
dependencies:
debug: 4.3.4
transitivePeerDependencies:
- supports-color
dev: true
/@kwsites/promise-deferred@1.1.1:
resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==}
dev: true
/@lit-labs/ssr-dom-shim@1.1.2:
resolution: {integrity: sha512-jnOD+/+dSrfTWYfSXBXlo5l5f0q1UuJo3tkbMDCYA2lKUYq79jaxqtGEvnRoh049nt1vdo1+45RinipU6FGY2g==}
dev: true
@ -3402,6 +3420,10 @@ packages:
resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}
dev: true
/charenc@0.0.2:
resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==}
dev: true
/cheerio-select@2.1.0:
resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
dependencies:
@ -3530,6 +3552,10 @@ packages:
shebang-command: 2.0.0
which: 2.0.2
/crypt@0.0.2:
resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==}
dev: true
/crypto-random-string@2.0.0:
resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
engines: {node: '>=8'}
@ -4280,6 +4306,10 @@ packages:
has-tostringtag: 1.0.0
dev: true
/is-buffer@1.1.6:
resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
dev: true
/is-callable@1.2.7:
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
engines: {node: '>= 0.4'}
@ -4677,6 +4707,14 @@ packages:
resolution: {integrity: sha512-h4Yd392z9mST+dzc+yjuybOGFNOZjmXIPKWjxBd1Bb23r4SmDOsk2NYCU2BMUBGbSpZqwVsZYNq26QS3xfaT3Q==}
dev: true
/md5@2.3.0:
resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
dependencies:
charenc: 0.0.2
crypt: 0.0.2
is-buffer: 1.1.6
dev: true
/mdast-util-to-hast@13.0.2:
resolution: {integrity: sha512-U5I+500EOOw9e3ZrclN3Is3fRpw8c19SMyNZlZ2IS+7vLsNzb2Om11VpIVOR+/0137GhZsFEF6YiKD5+0Hr2Og==}
dependencies:
@ -5350,6 +5388,16 @@ packages:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
/simple-git@3.21.0:
resolution: {integrity: sha512-oTzw9248AF5bDTMk9MrxsRzEzivMlY+DWH0yWS4VYpMhNLhDWnN06pCtaUyPnqv/FpsdeNmRqmZugMABHRPdDA==}
dependencies:
'@kwsites/file-exists': 1.1.1
'@kwsites/promise-deferred': 1.1.1
debug: 4.3.4
transitivePeerDependencies:
- supports-color
dev: true
/sirv@2.0.3:
resolution: {integrity: sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==}
engines: {node: '>= 10'}