feat(next): add change log to the footer (#233)
parent
2547c0ba82
commit
26ff059973
|
@ -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']
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
||||
|
|
|
@ -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']
|
|
@ -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 []
|
||||
}
|
||||
}
|
|
@ -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 />
|
||||
`
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
})
|
||||
})
|
||||
}
|
|
@ -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()
|
||||
))
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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>`)
|
||||
}
|
|
@ -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$/],
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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'}
|
||||
|
|
Loading…
Reference in New Issue