diff --git a/.stylelintrc.yml b/.stylelintrc.yml index a12f147ce1..e376d5e13b 100644 --- a/.stylelintrc.yml +++ b/.stylelintrc.yml @@ -11,11 +11,17 @@ ignoreFiles: - '**/empty.scss' rules: - declaration-block-no-redundant-longhand-properties: true media-feature-range-notation: prefix no-descending-specificity: null # workaround for mixed-declarations no-duplicate-selectors: null + declaration-block-no-redundant-longhand-properties: + - true + - ignoreShorthands: + - 'margin-block' + - 'margin-inline' + - 'padding-inline' + - 'padding-block' overrides: - files: diff --git a/docs/.vuepress/public/images/git/changelog-en.png b/docs/.vuepress/public/images/git/changelog-en.png new file mode 100644 index 0000000000..52f4576d80 Binary files /dev/null and b/docs/.vuepress/public/images/git/changelog-en.png differ diff --git a/docs/.vuepress/public/images/git/changelog-zh.png b/docs/.vuepress/public/images/git/changelog-zh.png new file mode 100644 index 0000000000..e7eea82852 Binary files /dev/null and b/docs/.vuepress/public/images/git/changelog-zh.png differ diff --git a/docs/.vuepress/public/images/git/contributor-en.png b/docs/.vuepress/public/images/git/contributor-en.png new file mode 100644 index 0000000000..6fcaf2e372 Binary files /dev/null and b/docs/.vuepress/public/images/git/contributor-en.png differ diff --git a/docs/.vuepress/public/images/git/contributor-zh.png b/docs/.vuepress/public/images/git/contributor-zh.png new file mode 100644 index 0000000000..94c56d50ea Binary files /dev/null and b/docs/.vuepress/public/images/git/contributor-zh.png differ diff --git a/docs/plugins/development/git.md b/docs/plugins/development/git.md index 7c9ab1e0c6..1508d8a469 100644 --- a/docs/plugins/development/git.md +++ b/docs/plugins/development/git.md @@ -192,6 +192,39 @@ This plugin will significantly slow down the speed of data preparation, especial Page filter, if it returns `true`, the page will collect git information. +### locales + +- Type: `Record` + + ```ts + export interface GitLocaleData extends LocaleData { + /** + * Contributors title + */ + contributors: string + /** + * Changelog title + */ + changelog: string + /** + * Changelog on time + */ + changelogOn: string + /** + * Changelog button + */ + changelogButton: string + /** + * Latest updated + */ + latestUpdated: string + } + ``` + +- Details: + + Locales configuration, used in the [Git Component](#component). + ## Frontmatter ### gitInclude @@ -343,3 +376,55 @@ interface GitChangelog { The changelog of the page. This attribute would also include contributors to the files listed in [gitInclude](#gitinclude). + +## Git Component{#component} + +The plugin provides components related to Git information, which can be used in themes. + +The components are imported as follows: + +```ts +import { Changelog, Contributors } from '@vuepress/plugin-git/client' +``` + +### Contributors + +List the contributor information for the current page. + +```vue{8} + + + +``` + +**Effect Preview:** + +![contributors](/images/git/contributor-en.png) + +### Changelog + +List the changelog of the current page. + +```vue{8} + + + +``` + +**Effect Preview:** + +![changelog](/images/git/changelog-en.png) diff --git a/docs/zh/plugins/development/git.md b/docs/zh/plugins/development/git.md index 7e0dceac3b..108de313e0 100644 --- a/docs/zh/plugins/development/git.md +++ b/docs/zh/plugins/development/git.md @@ -185,6 +185,39 @@ export default { 页面过滤器,如果返回 `true` ,该页面将收集 git 信息 +### locales + +- 类型: `Record` + + ```ts + export interface GitLocaleData extends LocaleData { + /** + * 贡献者 标题 + */ + contributors: string + /** + * 更新日志 标题 + */ + changelog: string + /** + * 更新 `于` 文本 + */ + changelogOn: string + /** + * 查看更新日志 文本 + */ + changelogButton: string + /** + * 最近更新 文本 + */ + latestUpdated: string + } + ``` + +- 详情: + + 多语言配置,在 [Git 组件](#component) 中使用。 + ## Frontmatter ### gitInclude @@ -335,3 +368,55 @@ interface GitChangelog { 页面的变更历史记录。 该属性将会包含 [gitInclude](#gitinclude) 所列文件的变更历史记录。 + +## Git 组件{#component} + +插件提供了与 Git 信息相关的组件,可以在主题中使用。 + +组件通过以下方式导入: + +```ts +import { Changelog, Contributors } from '@vuepress/plugin-git/client' +``` + +### Contributors + +列出当前页面的贡献者信息。 + +```vue{8} + + + +``` + +**效果预览:** + +![contributors](/images/git/contributor-zh.png) + +### Changelog + +列出当前页面的变更历史记录。 + +```vue{8} + + + +``` + +**效果预览:** + +![changelog](/images/git/changelog-zh.png) diff --git a/plugins/development/plugin-git/package.json b/plugins/development/plugin-git/package.json index ef9221e31e..c3196d3d66 100644 --- a/plugins/development/plugin-git/package.json +++ b/plugins/development/plugin-git/package.json @@ -22,6 +22,7 @@ "type": "module", "exports": { ".": "./lib/node/index.js", + "./client": "./lib/client/index.js", "./package.json": "./package.json" }, "main": "./lib/node/index.js", @@ -32,10 +33,14 @@ "scripts": { "build": "tsc -b tsconfig.build.json", "bundle": "rollup -c rollup.config.ts --configPlugin esbuild", - "clean": "rimraf --glob ./lib ./*.tsbuildinfo" + "clean": "rimraf --glob ./lib ./*.tsbuildinfo", + "style": "sass src:lib --embed-sources --style=compressed --pkg-importer=node" }, "dependencies": { - "execa": "^9.5.2" + "@vuepress/helper": "workspace:*", + "@vueuse/core": "^12.7.0", + "execa": "^9.5.2", + "vue": "^3.5.13" }, "peerDependencies": { "vuepress": "catalog:" diff --git a/plugins/development/plugin-git/rollup.config.ts b/plugins/development/plugin-git/rollup.config.ts index 8f6d855dee..096a9d40e4 100644 --- a/plugins/development/plugin-git/rollup.config.ts +++ b/plugins/development/plugin-git/rollup.config.ts @@ -1,5 +1,8 @@ import { rollupBundle } from '../../../scripts/rollup.js' -export default rollupBundle('node/index', { - external: ['execa'], -}) +export default [ + ...rollupBundle('node/index', { + external: ['execa'], + }), + ...rollupBundle('client/index'), +] diff --git a/plugins/development/plugin-git/src/client/components/Changelog.ts b/plugins/development/plugin-git/src/client/components/Changelog.ts new file mode 100644 index 0000000000..1c4d9b759b --- /dev/null +++ b/plugins/development/plugin-git/src/client/components/Changelog.ts @@ -0,0 +1,137 @@ +import { useToggle } from '@vueuse/core' +import type { FunctionalComponent } from 'vue' +import { computed, defineComponent, h } from 'vue' +import { usePageData, usePageFrontmatter, usePageLang } from 'vuepress/client' +import type { + GitChangelog, + GitPluginFrontmatter, + GitPluginPageData, +} from '../../shared/index.js' +import { useGitLocales } from '../composables/index.js' +import { VPHeader } from './VPHeader.js' + +import '../styles/vars.css' +import '../styles/changelog.css' + +type ResolvedChangelog = Omit & { datetime: string } + +export const Changelog = defineComponent({ + name: 'Changelog', + props: { + headerLevel: { + type: Number, + default: 2, + }, + title: String, + }, + setup(props) { + const page = usePageData() + const lang = usePageLang() + const frontmatter = usePageFrontmatter() + const locale = useGitLocales() + + const list = computed(() => { + if (frontmatter.value.changelog === false) return [] + + const formatter = new Intl.DateTimeFormat(lang.value, { + dateStyle: 'short', + }) + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + return (page.value.git?.changelog ?? []).map(({ date, ...item }) => { + const datetime = formatter.format(date) + return { datetime, ...item } + }) + }) + + const latestUpdated = computed(() => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + const latest = (page.value.git?.changelog ?? [])[0] + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!latest) return '' + + const formatter = new Intl.DateTimeFormat(lang.value, { + dateStyle: 'short', + timeStyle: 'short', + }) + return formatter.format(latest.date) + }) + + const [active, toggle] = useToggle() + + const ChangelogHeader: FunctionalComponent = () => + h('div', { class: 'changelog-header', onClick: toggle }, [ + h('div', { class: 'latest-updated' }, [ + h('span', { class: 'vpi-changelog' }), + h('span', [locale.value.latestUpdated || 'Latest updated:', ' ']), + h('span', { 'data-allow-mismatch': true }, latestUpdated.value), + ]), + h('div', [ + h('span', { class: 'vpi-changelog-menu' }), + h('span', locale.value.changelogButton || 'View All Changelog'), + ]), + ]) + + const ReleaseTag: FunctionalComponent<{ item: ResolvedChangelog }> = ({ + item, + }) => + h( + 'li', + { class: 'changelog release-tag' }, + h('div', [ + h('a', { class: 'link release-tag' }, h('code', item.tag)), + h('span', { 'class': 'datetime', 'data-allow-mismatch': true }, [ + locale.value.changelogOn || 'on', + ' ', + item.datetime, + ]), + ]), + ) + + const Commit: FunctionalComponent<{ item: ResolvedChangelog }> = ({ + item, + }) => + h('li', { class: 'changelog commit' }, [ + h( + item.commitUrl ? 'a' : 'span', + { + class: 'link hash', + href: item.commitUrl, + target: '_blank', + rel: 'noreferrer', + }, + [h('code', item.hash.slice(0, 5))], + ), + h('span', { class: 'divider' }, '-'), + h('span', { class: 'message', innerHTML: item.message }), + h('span', { 'class': 'datetime', 'data-allow-mismatch': true }, [ + locale.value.changelogOn || 'on', + ' ', + item.datetime, + ]), + ]) + + return () => + list.value.length + ? [ + h(VPHeader, { + level: props.headerLevel, + anchor: 'doc-changelog', + title: props.title || locale.value.changelog || 'Changelog', + }), + + h('div', { class: ['vp-changelog', { active: active.value }] }, [ + h(ChangelogHeader), + + h('ul', { class: 'changelog-list' }, [ + list.value.map((item) => + item.tag + ? h(ReleaseTag, { item, key: item.tag }) + : h(Commit, { item, key: item.hash }), + ), + ]), + ]), + ] + : null + }, +}) diff --git a/plugins/development/plugin-git/src/client/components/Contributors.ts b/plugins/development/plugin-git/src/client/components/Contributors.ts new file mode 100644 index 0000000000..e3f690ed85 --- /dev/null +++ b/plugins/development/plugin-git/src/client/components/Contributors.ts @@ -0,0 +1,71 @@ +import type { VNode } from 'vue' +import { computed, defineComponent, h } from 'vue' +import { usePageData, usePageFrontmatter } from 'vuepress/client' +import type { + GitContributor, + GitPluginFrontmatter, + GitPluginPageData, +} from '../../shared/index.js' +import { useGitLocales } from '../composables/index.js' +import { VPHeader } from './VPHeader.js' + +import '../styles/contributors.css' + +export const Contributors = defineComponent({ + name: 'Contributors', + props: { + headerLevel: { + type: Number, + default: 2, + }, + title: String, + }, + setup(props) { + const page = usePageData() + const frontmatter = usePageFrontmatter() + const locale = useGitLocales() + + const contributors = computed(() => { + if (frontmatter.value.contributors === false) return [] + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + return page.value.git?.contributors ?? [] + }) + + const Contributor = ({ + item: { name, url, avatar }, + }: { + item: GitContributor + }): VNode => + h( + url ? 'a' : 'span', + { + href: url, + target: '_blank', + rel: 'noreferrer', + class: 'contributor', + }, + [ + avatar ? h('img', { src: avatar, alt: name, class: 'avatar' }) : null, + h('span', { class: 'name' }, name), + ], + ) + + return () => + contributors.value.length + ? [ + h(VPHeader, { + level: props.headerLevel, + anchor: 'doc-contributors', + title: props.title || locale.value.contributors || 'Contributors', + }), + h( + 'div', + { class: 'vp-contributors' }, + contributors.value.map((item) => + h(Contributor, { item, key: item.name + item.email }), + ), + ), + ] + : null + }, +}) diff --git a/plugins/development/plugin-git/src/client/components/VPHeader.ts b/plugins/development/plugin-git/src/client/components/VPHeader.ts new file mode 100644 index 0000000000..7ef632b2d9 --- /dev/null +++ b/plugins/development/plugin-git/src/client/components/VPHeader.ts @@ -0,0 +1,19 @@ +import type { FunctionalComponent } from 'vue' +import { h } from 'vue' + +interface VPHeaderProps { + level?: number + title: string + anchor: string +} + +export const VPHeader: FunctionalComponent = ({ + level = 2, + title, + anchor, +}) => + h( + `h${level || 2}`, + { id: anchor, tabindex: '-1' }, + h('a', { href: `#${anchor}`, class: 'header-anchor' }, h('span', title)), + ) diff --git a/plugins/development/plugin-git/src/client/components/index.ts b/plugins/development/plugin-git/src/client/components/index.ts new file mode 100644 index 0000000000..69f818ea46 --- /dev/null +++ b/plugins/development/plugin-git/src/client/components/index.ts @@ -0,0 +1,3 @@ +export * from './Contributors.js' +export * from './Changelog.js' +export * from './VPHeader.js' diff --git a/plugins/development/plugin-git/src/client/composables/index.ts b/plugins/development/plugin-git/src/client/composables/index.ts new file mode 100644 index 0000000000..6d752166cb --- /dev/null +++ b/plugins/development/plugin-git/src/client/composables/index.ts @@ -0,0 +1 @@ +export * from './useGitLocales.js' diff --git a/plugins/development/plugin-git/src/client/composables/useGitLocales.ts b/plugins/development/plugin-git/src/client/composables/useGitLocales.ts new file mode 100644 index 0000000000..2a1637f951 --- /dev/null +++ b/plugins/development/plugin-git/src/client/composables/useGitLocales.ts @@ -0,0 +1,10 @@ +import { useLocaleConfig } from '@vuepress/helper/client' +import type { ComputedRef } from 'vue' +import type { GitLocaleData, GitLocales } from '../../shared/index.js' + +declare const __GIT_LOCALES__: GitLocales + +export const locales = __GIT_LOCALES__ + +export const useGitLocales = (): ComputedRef> => + useLocaleConfig(locales) diff --git a/plugins/development/plugin-git/src/client/index.ts b/plugins/development/plugin-git/src/client/index.ts new file mode 100644 index 0000000000..a29d02d276 --- /dev/null +++ b/plugins/development/plugin-git/src/client/index.ts @@ -0,0 +1,3 @@ +export * from './components/index.js' +export * from './composables/index.js' +export type * from '../shared/index.js' diff --git a/plugins/development/plugin-git/src/client/styles/changelog.scss b/plugins/development/plugin-git/src/client/styles/changelog.scss new file mode 100644 index 0000000000..e099284fe7 --- /dev/null +++ b/plugins/development/plugin-git/src/client/styles/changelog.scss @@ -0,0 +1,160 @@ +.vp-changelog { + margin-block-start: 2rem; + margin-block-end: 1rem; + padding-inline-start: 1rem; + padding-inline-end: 1rem; + border-radius: 8px; + + background-color: var(--changelog-bg); + color: var(--changelog-c-text); + + font-size: var(--changelog-font-size); + line-height: 1.7; + + transition: background-color var(--vp-t-color); + + a { + text-decoration: none !important; + + &::after { + display: none !important; + } + } + + .changelog-header { + display: flex; + align-items: center; + justify-content: space-between; + + padding-block-start: 1rem; + padding-block-end: 1rem; + + font-weight: bold; + + cursor: pointer; + + @media (max-width: 419px) { + flex-wrap: wrap; + } + + .vpi-changelog, + .vpi-changelog-menu { + display: inline-block; + vertical-align: middle; + + width: 1.2em; + height: 1.2em; + margin-inline-end: 4px; + + background-color: currentcolor; + color: var(--vp-c-text-mute); + + mask: var(--icon) no-repeat; + mask-size: 100% 100%; + + transition: color var(--vp-t-color); + + transform: translateY(-1px); + } + + .vpi-changelog { + --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' d='M2.71 10.96a6.5 6.5 0 1 0-.69-3.53M2 8l1.5-1.5M2 8L.5 6.5m8 2v-4m0 4h3'/%3E%3C/svg%3E"); + } + + .vpi-changelog-menu { + --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='M4 5h0.01'/%3E%3Cpath d='M8 5h12'/%3E%3Cpath d='M4 10h0.01'/%3E%3Cpath d='M8 10h12'/%3E%3Cpath d='M4 15h0.01'/%3E%3Cpath d='M8 15h12'/%3E%3Cpath d='M4 20h0.01'/%3E%3Cpath d='M8 20h12'/%3E%3C/g%3E%3C/svg%3E"); + } + } + + .changelog-list { + display: none; + + margin-block-start: 0.5rem; + margin-block-end: 0.5rem; + padding-inline-start: 0; + + list-style: none; + } + + &.active { + padding-block-start: 0; + padding-block-end: 0.5rem; + + .changelog-header { + padding-block-end: 0.5rem; + } + + .changelog-list { + display: block; + } + } + + .changelog { + position: relative; + margin-block-start: 8px; + padding-inline-start: 20px; + + &:first-child { + margin-block-start: 0; + } + + &::before { + content: ''; + + position: absolute; + top: 3px; + left: 0; + + display: inline-block; + + width: 1.25em; + height: 1.25em; + + background-color: currentcolor; + color: var(--vp-c-text-subtle); + + mask: var(--icon) no-repeat; + mask-size: 100% 100%; + + transition: color var(--vp-t-color); + } + + &.commit::before { + --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 12a3 3 0 1 0 6 0a3 3 0 1 0-6 0m3-9v6m0 6v6'/%3E%3C/svg%3E"); + } + + &.release-tag::before { + --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='M6.5 7.5a1 1 0 1 0 2 0a1 1 0 1 0-2 0'/%3E%3Cpath d='M3 6v5.172a2 2 0 0 0 .586 1.414l7.71 7.71a2.41 2.41 0 0 0 3.408 0l5.592-5.592a2.41 2.41 0 0 0 0-3.408l-7.71-7.71A2 2 0 0 0 11.172 3H6a3 3 0 0 0-3 3'/%3E%3C/g%3E%3C/svg%3E"); + } + + .release-tag { + margin-inline-end: 4px; + text-decoration: none; + } + + .release-tag code { + font-weight: 500; + font-size: 14px; + } + + .hash { + margin-inline-end: 4px; + text-decoration: none; + } + + .divider { + margin-inline-end: 8px; + } + + .message { + margin-inline-end: 8px; + line-height: 1.7; + } + + .datetime { + color: var(--vp-c-text-subtle); + font-size: 0.75rem; + transition: color var(--vp-t-color); + } + } +} diff --git a/plugins/development/plugin-git/src/client/styles/contributors.scss b/plugins/development/plugin-git/src/client/styles/contributors.scss new file mode 100644 index 0000000000..8d52201103 --- /dev/null +++ b/plugins/development/plugin-git/src/client/styles/contributors.scss @@ -0,0 +1,30 @@ +.vp-contributors { + display: flex; + flex-wrap: wrap; + gap: 1rem 1.5rem; + align-items: center; + justify-content: flex-start; + + margin: 2rem 0 1rem; + + .contributor { + display: flex; + gap: 0.25rem; + align-items: center; + + &::after { + display: none !important; + } + + .avatar { + object-fit: contain; + width: 36px; + height: 36px; + border-radius: 50%; + } + } + + a.contributor { + text-decoration: none !important; + } +} diff --git a/plugins/development/plugin-git/src/client/styles/vars.css b/plugins/development/plugin-git/src/client/styles/vars.css new file mode 100644 index 0000000000..368959599b --- /dev/null +++ b/plugins/development/plugin-git/src/client/styles/vars.css @@ -0,0 +1,5 @@ +:root { + --changelog-bg: var(--vp-c-bg-alt); + --changelog-font-size: 0.875rem; + --changelog-c-text: var(--vp-c-text); +} diff --git a/plugins/development/plugin-git/src/node/gitPlugin.ts b/plugins/development/plugin-git/src/node/gitPlugin.ts index 6fb4c1e805..12f9792a0d 100644 --- a/plugins/development/plugin-git/src/node/gitPlugin.ts +++ b/plugins/development/plugin-git/src/node/gitPlugin.ts @@ -1,11 +1,13 @@ +import { getFullLocaleConfig } from '@vuepress/helper' import type { Page, Plugin } from 'vuepress/core' import { isPlainObject } from 'vuepress/shared' import { path } from 'vuepress/utils' import type { GitPluginFrontmatter, - GitPluginOptions, GitPluginPageData, -} from './options.js' +} from '../shared/index.js' +import { DEFAULT_LOCALES } from './locales.js' +import type { GitPluginOptions } from './options.js' import { resolveChangelog } from './resolveChangelog.js' import { resolveContributors } from './resolveContributors.js' import { checkGitRepo, getCommits, inferGitProvider } from './utils/index.js' @@ -19,14 +21,24 @@ export const gitPlugin = filter, // eslint-disable-next-line @typescript-eslint/no-deprecated transformContributors, + locales = {}, }: GitPluginOptions = {}): Plugin => (app) => { + const name = '@vuepress/plugin-git' const cwd = app.dir.source() const isGitRepoValid = checkGitRepo(cwd) const gitProvider = isGitRepoValid ? inferGitProvider(cwd) : null - return { - name: '@vuepress/plugin-git', + name, + + define: { + __GIT_LOCALES__: getFullLocaleConfig({ + app, + name, + default: DEFAULT_LOCALES, + config: locales, + }), + }, extendsPage: async ( page: Page, diff --git a/plugins/development/plugin-git/src/node/index.ts b/plugins/development/plugin-git/src/node/index.ts index b211e8e0a7..fbd696d81f 100644 --- a/plugins/development/plugin-git/src/node/index.ts +++ b/plugins/development/plugin-git/src/node/index.ts @@ -4,3 +4,4 @@ export * from './resolveContributors.js' export * from './utils/index.js' export type * from './options.js' export type * from './typings.js' +export type * from '../shared/index.js' diff --git a/plugins/development/plugin-git/src/node/locales.ts b/plugins/development/plugin-git/src/node/locales.ts new file mode 100644 index 0000000000..e4c76056a0 --- /dev/null +++ b/plugins/development/plugin-git/src/node/locales.ts @@ -0,0 +1,205 @@ +import type { DefaultLocaleInfo } from '@vuepress/helper' +import type { GitLocaleData } from '../shared/index.js' + +export const DEFAULT_LOCALES: DefaultLocaleInfo = [ + [ + ['en', 'en-US'], + { + contributors: 'Contributors', + changelog: 'Changelog', + changelogOn: 'on', + changelogButton: 'View All Changelog', + latestUpdated: 'Last Updated:', + }, + ], + [ + ['zh', 'zh-CN', 'zh-Hans'], + { + contributors: '贡献者', + changelog: '更新日志', + changelogOn: '于', + changelogButton: '查看所有更新日志', + latestUpdated: '最近更新:', + }, + ], + [ + ['zh-TW', 'zh-Hant'], + { + contributors: '貢獻者', + changelog: '更新日誌', + changelogOn: '於', + changelogButton: '查看所有更新日誌', + latestUpdated: '最近更新:', + }, + ], + [ + ['de', 'de-DE'], + { + contributors: 'Mitwirkende', + changelog: 'Aenderungen', + changelogOn: 'am', + changelogButton: 'Alle Aenderungen anzeigen', + latestUpdated: 'Zuletzt aktualisiert:', + }, + ], + [ + ['de-AT'], + { + contributors: 'Mitwirkende', + changelog: 'Aenderungen', + changelogOn: 'am', + changelogButton: 'Alle Aenderungen anzeigen', + latestUpdated: 'Zuletzt aktualisiert:', + }, + ], + [ + ['vi', 'vi-VN'], + { + contributors: 'Người tham gia', + changelog: 'Lịch sử thay đổi', + changelogOn: 'vào', + changelogButton: 'Xem tất cả lịch sử thay đổi', + latestUpdated: 'Cập nhật gần nhất:', + }, + ], + [ + ['uk'], + { + contributors: 'Учасники', + changelog: 'Історія змін', + changelogOn: 'в', + changelogButton: 'Показати всю історію змін', + latestUpdated: 'Останнє оновлення:', + }, + ], + [ + ['ru', 'ru-RU'], + { + contributors: 'Участники', + changelog: 'История изменений', + changelogOn: 'в', + changelogButton: 'Показать всю историю изменений', + latestUpdated: 'Последнее обновление:', + }, + ], + [ + ['br'], + { + contributors: 'Colaboradores', + changelog: 'Log de Alterações', + changelogOn: 'em', + changelogButton: 'Ver todas as Alterações', + latestUpdated: 'Atualizado em:', + }, + ], + [ + ['pl', 'pl-PL'], + { + contributors: 'Współpracownicy', + changelog: 'Zmiany', + changelogOn: 'w', + changelogButton: 'Zobacz wszystkie zmiany', + latestUpdated: 'Ostatnio zaktualizowano:', + }, + ], + [ + ['sk', 'sk-SK'], + { + contributors: 'Pracovnici', + changelog: 'Zmeny', + changelogOn: 'na', + changelogButton: 'Zobrazit vsetky zmeny', + latestUpdated: 'Poslednou aktualizaciu:', + }, + ], + [ + ['fr', 'fr-FR'], + { + contributors: 'Contribueurs', + changelog: 'Historique des modifications', + changelogOn: 'le', + changelogButton: "Voir l'historique des modifications", + latestUpdated: 'Dernière mise à jour:', + }, + ], + [ + ['es', 'es-ES'], + { + contributors: 'Colaboradores', + changelog: 'Historial de cambios', + changelogOn: 'el', + changelogButton: 'Ver el historial de cambios', + latestUpdated: 'Actualizado el:', + }, + ], + [ + ['ja', 'ja-JP'], + { + contributors: '貢献者', + changelog: '変更履歴', + changelogOn: '日', + changelogButton: '全ての変更履歴を表示', + latestUpdated: '最終更新:', + }, + ], + [ + ['tr', 'tr-TR'], + { + contributors: 'Katkıda bulunanlar', + changelog: 'Düzenleme Girişimleri', + changelogOn: 'de', + changelogButton: 'Tüm düzenleme girişimlerini göster', + latestUpdated: 'Son günceleme:', + }, + ], + [ + ['ko', 'ko-KO'], + { + contributors: '기업자', + changelog: '변경 기록', + changelogOn: '에서', + changelogButton: '변경 기록 별 상세 내용 바보기', + latestUpdated: '최근 수정:', + }, + ], + [ + ['fi', 'fi-FI'], + { + contributors: 'Osallistujat', + changelog: 'Muutokset', + changelogOn: 'kun', + changelogButton: 'Näytä kaikki muutokset', + latestUpdated: 'Viimeksi paivittynyt:', + }, + ], + [ + ['hu', 'hu-HU'], + { + contributors: 'Szerkesztők', + changelog: 'Modosítások', + changelogOn: 'ben', + changelogButton: 'Minden modosítás megtekintése', + latestUpdated: 'Legutóbb frissítés:', + }, + ], + [ + ['id', 'id-ID'], + { + contributors: 'Pembuat', + changelog: 'Perubahan', + changelogOn: 'pada', + changelogButton: 'Lihat semua perubahan', + latestUpdated: 'Diperbarui terakhir:', + }, + ], + [ + ['nl', 'nl-NL'], + { + contributors: 'Bijdragers', + changelog: 'Wijzigingen', + changelogOn: 'op', + changelogButton: 'Bekijk alle wijzigingen', + latestUpdated: 'Laatst bijgewerkt:', + }, + ], +] diff --git a/plugins/development/plugin-git/src/node/options.ts b/plugins/development/plugin-git/src/node/options.ts index 091ee244da..ebe7ac0d98 100644 --- a/plugins/development/plugin-git/src/node/options.ts +++ b/plugins/development/plugin-git/src/node/options.ts @@ -1,5 +1,5 @@ -import type { Page, PageFrontmatter } from 'vuepress' -import type { GitChangelog, GitContributor } from './typings.js' +import type { Page } from 'vuepress' +import type { GitContributor, GitLocales } from '../shared/index.js' /** * Contributor information @@ -191,47 +191,11 @@ export interface GitPluginOptions { * Functions to transform contributors, e.g. remove duplicates ones and sort them */ transformContributors?: (contributors: GitContributor[]) => GitContributor[] -} - -export interface GitPluginFrontmatter extends PageFrontmatter { - gitInclude?: string[] /** - * Whether to get the contributors of a page + * Localization config * - * - If the value is `false`, it will be ignored - * - If the value is `string[]`, it will be used as the list of extra contributors - */ - contributors?: string[] | boolean - - /** - * Whether to get the changelog of a page - */ - changelog?: boolean -} - -export interface GitPluginPageData extends Record { - git: GitData -} - -export interface GitData { - /** - * Unix timestamp in milliseconds of the first commit - */ - createdTime?: number - - /** - * Unix timestamp in milliseconds of the last commit - */ - updatedTime?: number - - /** - * Contributors of all commits - */ - contributors?: GitContributor[] - - /** - * Changelog of a page + * 本地化配置 */ - changelog?: GitChangelog[] + locales?: GitLocales } diff --git a/plugins/development/plugin-git/src/node/resolveChangelog.ts b/plugins/development/plugin-git/src/node/resolveChangelog.ts index 009befe06d..954c39cf3a 100644 --- a/plugins/development/plugin-git/src/node/resolveChangelog.ts +++ b/plugins/development/plugin-git/src/node/resolveChangelog.ts @@ -1,10 +1,7 @@ import type { App } from 'vuepress' +import type { GitChangelog } from '../shared/index.js' import type { ChangelogOptions, ContributorInfo } from './options.js' -import type { - GitChangelog, - KnownGitProvider, - MergedRawCommit, -} from './typings.js' +import type { KnownGitProvider, MergedRawCommit } from './typings.js' import { getContributorInfo, getUserNameWithNoreplyEmail, diff --git a/plugins/development/plugin-git/src/node/resolveContributors.ts b/plugins/development/plugin-git/src/node/resolveContributors.ts index 404e17b57b..4d25591096 100644 --- a/plugins/development/plugin-git/src/node/resolveContributors.ts +++ b/plugins/development/plugin-git/src/node/resolveContributors.ts @@ -1,9 +1,6 @@ +import type { GitContributor } from '../shared/index.js' import type { ContributorsOptions } from './options.js' -import type { - GitContributor, - KnownGitProvider, - MergedRawCommit, -} from './typings.js' +import type { KnownGitProvider, MergedRawCommit } from './typings.js' import { digestSHA256, getContributorInfo, @@ -17,7 +14,8 @@ export const getRawContributors = ( ): GitContributor[] => { const contributors = new Map() - for (const commit of commits.reverse()) { + // copy and reverse commits + for (const commit of [...commits].reverse()) { const authors = [ { name: commit.author, email: commit.email }, ...commit.coAuthors, diff --git a/plugins/development/plugin-git/src/node/typings.ts b/plugins/development/plugin-git/src/node/typings.ts index 3eda84df0c..f31ca43eac 100644 --- a/plugins/development/plugin-git/src/node/typings.ts +++ b/plugins/development/plugin-git/src/node/typings.ts @@ -1,16 +1,9 @@ +import type { CoAuthorInfo } from '../shared/index.js' /** * Git provider */ export type KnownGitProvider = 'bitbucket' | 'gitee' | 'github' | 'gitlab' -/** - * Co-author information - */ -export interface CoAuthorInfo { - name: string - email: string -} - export interface RawCommit { /** * File path @@ -54,71 +47,3 @@ export interface RawCommit { export interface MergedRawCommit extends Omit { filepaths: string[] } - -export interface GitContributor { - /** - * Contributor display name - */ - name: string - /** - * Contributor email - */ - email: string - - /** - * Contributor username on the git hosting service - */ - username: string - /** - * Number of commits - */ - commits: number - /** - * Contributor avatar - */ - avatar?: string - /** - * The url of the contributor - */ - url?: string -} - -export interface GitChangelog { - /** - * Commit hash - */ - hash: string - /** - * Unix timestamp in milliseconds - */ - date: number - /** - * Commit message - */ - message: string - /** - * The url of the commit - */ - commitUrl?: string - /** - * release tag - */ - tag?: string - /** - * The url of the release tag - */ - tagUrl?: string - /** - * Commit author name - */ - author: string - /** - * Commit author email - */ - email: string - - /** - * The co-authors of the commit - */ - coAuthors?: CoAuthorInfo[] -} diff --git a/plugins/development/plugin-git/src/node/utils/getCommits.ts b/plugins/development/plugin-git/src/node/utils/getCommits.ts index 195bd830e3..ab53b2188a 100644 --- a/plugins/development/plugin-git/src/node/utils/getCommits.ts +++ b/plugins/development/plugin-git/src/node/utils/getCommits.ts @@ -1,6 +1,7 @@ import { execa } from 'execa' +import type { GitContributor } from '../../shared/index.js' import type { GitPluginOptions } from '../options.js' -import type { GitContributor, MergedRawCommit, RawCommit } from '../typings.js' +import type { MergedRawCommit, RawCommit } from '../typings.js' const SPLIT_CHAR = '[GIT_LOG_COMMIT_END]' const RE_SPLIT = /\[GIT_LOG_COMMIT_END\]$/ diff --git a/plugins/development/plugin-git/src/shared/index.ts b/plugins/development/plugin-git/src/shared/index.ts new file mode 100644 index 0000000000..650f355eb4 --- /dev/null +++ b/plugins/development/plugin-git/src/shared/index.ts @@ -0,0 +1,154 @@ +import type { LocaleConfig, LocaleData, PageFrontmatter } from 'vuepress' + +/** + * Co-author information + */ +export interface CoAuthorInfo { + name: string + email: string +} + +export interface GitContributor { + /** + * Contributor display name + */ + name: string + /** + * Contributor email + */ + email: string + + /** + * Contributor username on the git hosting service + */ + username: string + /** + * Number of commits + */ + commits: number + /** + * Contributor avatar + */ + avatar?: string + /** + * The url of the contributor + */ + url?: string +} + +export interface GitChangelog { + /** + * Commit hash + */ + hash: string + /** + * Unix timestamp in milliseconds + */ + date: number + /** + * Commit message + */ + message: string + /** + * The url of the commit + */ + commitUrl?: string + /** + * release tag + */ + tag?: string + /** + * The url of the release tag + */ + tagUrl?: string + /** + * Commit author name + */ + author: string + /** + * Commit author email + */ + email: string + + /** + * The co-authors of the commit + */ + coAuthors?: CoAuthorInfo[] +} + +export interface GitPluginFrontmatter extends PageFrontmatter { + gitInclude?: string[] + + /** + * Whether to get the contributors of a page + * + * - If the value is `false`, it will be ignored + * - If the value is `string[]`, it will be used as the list of extra contributors + */ + contributors?: string[] | boolean + + /** + * Whether to get the changelog of a page + */ + changelog?: boolean +} + +export interface GitPluginPageData extends Record { + git: GitData +} + +export interface GitData { + /** + * Unix timestamp in milliseconds of the first commit + */ + createdTime?: number + + /** + * Unix timestamp in milliseconds of the last commit + */ + updatedTime?: number + + /** + * Contributors of all commits + */ + contributors?: GitContributor[] + + /** + * Changelog of a page + */ + changelog?: GitChangelog[] +} + +export type GitLocales = LocaleConfig + +export interface GitLocaleData extends LocaleData { + /** + * Contributors title + * @default 'Contributors' + */ + contributors: string + + /** + * Changelog title + * @default 'Changelog' + */ + changelog: string + + /** + * Changelog on time + * @default 'on' + */ + changelogOn: string + + /** + * Changelog button + * @default 'View All Changelog' + */ + changelogButton: string + + /** + * Latest updated + * @default 'Latest updated:' + */ + latestUpdated: string +} diff --git a/plugins/development/plugin-git/tsconfig.build.json b/plugins/development/plugin-git/tsconfig.build.json index 1e7fd0d655..7149386567 100644 --- a/plugins/development/plugin-git/tsconfig.build.json +++ b/plugins/development/plugin-git/tsconfig.build.json @@ -2,7 +2,8 @@ "extends": "../../../tsconfig.build.json", "compilerOptions": { "rootDir": "./src", - "outDir": "./lib" + "outDir": "./lib", + "types": ["vuepress/client-types", "vite/client", "webpack-env"] }, "include": ["./src"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 19a4ed0641..2ad8402512 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -406,9 +406,18 @@ importers: plugins/development/plugin-git: dependencies: + '@vuepress/helper': + specifier: workspace:* + version: link:../../../tools/helper + '@vueuse/core': + specifier: ^12.7.0 + version: 12.7.0(typescript@5.7.3) execa: specifier: ^9.5.2 version: 9.5.2 + vue: + specifier: ^3.5.13 + version: 3.5.13(typescript@5.7.3) vuepress: specifier: 'catalog:' version: 2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.5)(jiti@2.4.2)(lightningcss@1.29.1)(sass-embedded@1.85.0)(sass@1.85.0)(terser@5.39.0)(tsx@4.19.3)(typescript@5.7.3)(yaml@2.7.0))(@vuepress/bundler-webpack@2.0.0-rc.20(esbuild@0.25.0)(typescript@5.7.3))(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)) @@ -1278,9 +1287,6 @@ importers: '@vuepress/bundler-webpack': specifier: 'catalog:' version: 2.0.0-rc.20(esbuild@0.25.0)(typescript@5.7.3) - '@vuepress/plugin-git': - specifier: workspace:* - version: link:../../plugins/development/plugin-git domhandler: specifier: 5.0.3 version: 5.0.3 diff --git a/themes/theme-default/src/node/defaultTheme.ts b/themes/theme-default/src/node/defaultTheme.ts index bbc257632e..9dda60a701 100644 --- a/themes/theme-default/src/node/defaultTheme.ts +++ b/themes/theme-default/src/node/defaultTheme.ts @@ -138,7 +138,8 @@ export const defaultTheme = ({ ? gitPlugin({ createdTime: false, updatedTime: localeOptions.lastUpdated !== false, - contributors: localeOptions.contributors !== false, + changelog: { repoUrl: localeOptions.repo || '' }, + ...(isPlainObject(themePlugins.git) ? themePlugins.git : {}), }) : [], diff --git a/themes/theme-default/src/node/typings.ts b/themes/theme-default/src/node/typings.ts index f59b2e47b3..5193d7aa33 100644 --- a/themes/theme-default/src/node/typings.ts +++ b/themes/theme-default/src/node/typings.ts @@ -1,5 +1,6 @@ import type { BackToTopPluginOptions } from '@vuepress/plugin-back-to-top' import type { CopyCodePluginOptions } from '@vuepress/plugin-copy-code' +import type { GitPluginOptions } from '@vuepress/plugin-git' import type { LinksCheckPluginOptions } from '@vuepress/plugin-links-check' import type { MarkdownHintPluginOptions } from '@vuepress/plugin-markdown-hint' import type { MarkdownTabPluginOptions } from '@vuepress/plugin-markdown-tab' @@ -26,7 +27,7 @@ export interface DefaultThemePluginsOptions { /** * Enable @vuepress/plugin-git or not */ - git?: boolean + git?: GitPluginOptions | boolean /** * Enable @vuepress/plugin-markdown-hint or not diff --git a/tools/helper/package.json b/tools/helper/package.json index a0dfcce29b..667750b2d8 100644 --- a/tools/helper/package.json +++ b/tools/helper/package.json @@ -62,7 +62,6 @@ "@types/connect": "3.4.38", "@vuepress/bundler-vite": "catalog:", "@vuepress/bundler-webpack": "catalog:", - "@vuepress/plugin-git": "workspace:*", "domhandler": "5.0.3", "vite": "~6.1.1" },