From 26ff059973690e3ba236655eeae7ff69210fd292 Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Wed, 20 Dec 2023 08:41:16 +0800 Subject: [PATCH] feat(next): add change log to the footer (#233) --- docs/.vitepress/components.d.ts | 1 + docs/.vitepress/config.ts | 7 +- docs/.vitepress/meta.ts | 6 ++ docs/.vitepress/plugins/changelog.ts | 92 +++++++++++++++++++ docs/.vitepress/plugins/markdownTransform.ts | 8 ++ .../.vitepress/theme/components/Changelog.vue | 66 +++++++++++++ .../.vitepress/theme/composables/changelog.ts | 26 ++++++ docs/.vitepress/theme/composables/route.ts | 9 ++ docs/.vitepress/types.ts | 18 ++++ docs/.vitepress/utils.ts | 22 +++++ docs/vite.config.ts | 2 + package.json | 2 + pnpm-lock.yaml | 48 ++++++++++ 13 files changed, 304 insertions(+), 3 deletions(-) create mode 100644 docs/.vitepress/meta.ts create mode 100644 docs/.vitepress/plugins/changelog.ts create mode 100644 docs/.vitepress/theme/components/Changelog.vue create mode 100644 docs/.vitepress/theme/composables/changelog.ts create mode 100644 docs/.vitepress/theme/composables/route.ts create mode 100644 docs/.vitepress/types.ts create mode 100644 docs/.vitepress/utils.ts diff --git a/docs/.vitepress/components.d.ts b/docs/.vitepress/components.d.ts index fffaf8d..2c13437 100644 --- a/docs/.vitepress/components.d.ts +++ b/docs/.vitepress/components.d.ts @@ -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'] diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 686668e..40e9cc5 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -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 }, diff --git a/docs/.vitepress/meta.ts b/docs/.vitepress/meta.ts new file mode 100644 index 0000000..5da46ae --- /dev/null +++ b/docs/.vitepress/meta.ts @@ -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'] diff --git a/docs/.vitepress/plugins/changelog.ts b/docs/.vitepress/plugins/changelog.ts new file mode 100644 index 0000000..16776fc --- /dev/null +++ b/docs/.vitepress/plugins/changelog.ts @@ -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 = {} + + 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 [] + } +} diff --git a/docs/.vitepress/plugins/markdownTransform.ts b/docs/.vitepress/plugins/markdownTransform.ts index 7f55ed7..6c930b2 100644 --- a/docs/.vitepress/plugins/markdownTransform.ts +++ b/docs/.vitepress/plugins/markdownTransform.ts @@ -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)+^---$)/ `) + +const pageFooterTemplate = (code: string) => `${code} + +## 变更记录 + + +` diff --git a/docs/.vitepress/theme/components/Changelog.vue b/docs/.vitepress/theme/components/Changelog.vue new file mode 100644 index 0000000..2bf9fe4 --- /dev/null +++ b/docs/.vitepress/theme/components/Changelog.vue @@ -0,0 +1,66 @@ + + + diff --git a/docs/.vitepress/theme/composables/changelog.ts b/docs/.vitepress/theme/composables/changelog.ts new file mode 100644 index 0000000..a0de4fe --- /dev/null +++ b/docs/.vitepress/theme/composables/changelog.ts @@ -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) { + return computed(() => { + 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 + }) + }) +} diff --git a/docs/.vitepress/theme/composables/route.ts b/docs/.vitepress/theme/composables/route.ts new file mode 100644 index 0000000..c11b97e --- /dev/null +++ b/docs/.vitepress/theme/composables/route.ts @@ -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() + )) +} diff --git a/docs/.vitepress/types.ts b/docs/.vitepress/types.ts new file mode 100644 index 0000000..e72acbf --- /dev/null +++ b/docs/.vitepress/types.ts @@ -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 +} diff --git a/docs/.vitepress/utils.ts b/docs/.vitepress/utils.ts new file mode 100644 index 0000000..a6a4674 --- /dev/null +++ b/docs/.vitepress/utils.ts @@ -0,0 +1,22 @@ +import { githubRepoLink } from "./meta" + +export function renderMarkdown(markdownText = '') { + const htmlText = markdownText + .replace(/^### (.*$)/gim, '

$1

') + .replace(/^## (.*$)/gim, '

$1

') + .replace(/^# (.*$)/gim, '

$1

') + .replace(/^\> (.*$)/gim, '
$1
') + .replace(/\*\*(.*)\*\*/gim, '$1') + .replace(/\*(.*)\*/gim, '$1') + .replace(/!\[(.*?)\]\((.*?)\)/gim, '\'$1\'') + .replace(/\[(.*?)\]\((.*?)\)/gim, '$1') + .replace(/`(.*?)`/gim, '$1') + .replace(/\n$/gim, '
') + + return htmlText.trim() +} + +export function renderCommitMessage(msg: string) { + return renderMarkdown(msg) + .replace(/\#([0-9]+)/g, `#$1`) +} diff --git a/docs/vite.config.ts b/docs/vite.config.ts index e527ea0..4578e08 100644 --- a/docs/vite.config.ts +++ b/docs/vite.config.ts @@ -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$/], diff --git a/package.json b/package.json index 446ac4e..51ce6bd 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b247c69..46a015a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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'}