pull/5128/merge
WizardsBowl 4 days ago committed by GitHub
commit 220ebdaeae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -34,6 +34,7 @@ export default defineConfig({
},
lastUpdated: true,
created: true,
cleanUrls: true,
metaChunk: true,

@ -132,6 +132,7 @@ function sidebarReference(): DefaultTheme.SidebarItem[] {
{ text: 'Prev / Next Links', link: 'prev-next-links' },
{ text: 'Edit Link', link: 'edit-link' },
{ text: 'Last Updated Timestamp', link: 'last-updated' },
{ text: 'Created Timestamp', link: 'created' },
{ text: 'Search', link: 'search' },
{ text: 'Carbon Ads', link: 'carbon-ads' }
]

@ -342,6 +342,46 @@ export interface LastUpdatedOptions {
}
```
## created
- Type: `CreatedOptions`
Allows customization for the created text and date format.
```ts
export default {
themeConfig: {
created: {
text: 'Created at',
formatOptions: {
dateStyle: 'full',
timeStyle: 'medium'
}
}
}
}
```
```ts
export interface CreatedOptions {
/**
* Set custom created text.
*
* @default 'Created'
*/
text?: string
/**
* Set options for created time formatting.
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat#using_options
*
* @default
* { dateStyle: 'short', timeStyle: 'short' }
*/
formatOptions?: Intl.DateTimeFormatOptions & { forceLocale?: boolean }
}
```
## algolia
- Type: `AlgoliaSearch`

@ -0,0 +1,29 @@
# Created
The created time of the content will be displayed in the lower right corner of the page. To enable it, add `created` options to your config.
::: info
VitePress displays the "created" time using the timestamp of the first Git commit for each file. To enable this, the Markdown file must be committed to Git.
Internally, VitePress runs `git log -1 --pretty=%at --follow --diff-filter=A` on each file to retrieve its timestamp. If all pages show the same created time, please refer to the same paragraph on the [Last Updated](./default-theme-last-updated) page.
:::
## Site-Level Config
```js
export default {
created: true
}
```
## Frontmatter Config
This can be disabled per-page using the `created` option on frontmatter:
```yaml
---
created: false
---
```
Also refer [Default Theme: Created](./default-theme-config#created) for more details. Any truthy value at theme-level will also enable the feature unless explicitly disabled at site or page level.

@ -181,6 +181,19 @@ lastUpdated: false
---
```
### created
- Type: `boolean | Date`
- Default: `true`
Whether to display [created](./default-theme-created) text in the footer of the current page. If a datetime is specified, it will be displayed instead of the first git commit timestamp.
```yaml
---
created: false
---
```
### editLink
- Type: `boolean`

@ -59,6 +59,7 @@ interface PageData {
params?: Record<string, any>
isNotFound?: boolean
lastUpdated?: number
created?: number
}
```

@ -514,6 +514,15 @@ Whether to get the last updated timestamp for each page using Git. The timestamp
When using the default theme, enabling this option will display each page's last updated time. You can customize the text via [`themeConfig.lastUpdatedText`](./default-theme-config#lastupdatedtext) option.
### created
- Type: `boolean`
- Default: `false`
Whether to get the created timestamp for each page using Git. The timestamp will be included in each page's page data, accessible via [`useData`](./runtime-api#usedata).
When using the default theme, enabling this option will display each page's created time. You can customize the text via [`themeConfig.created.text`](./default-theme-config#created) option.
## Customization
### markdown

@ -40,6 +40,10 @@ export default defineAdditionalConfig({
text: '最后更新于'
},
created: {
text: '创建于'
},
notFound: {
title: '页面未找到',
quote:
@ -160,6 +164,7 @@ function sidebarReference(): DefaultTheme.SidebarItem[] {
{ text: '上下页链接', link: 'prev-next-links' },
{ text: '编辑链接', link: 'edit-link' },
{ text: '最后更新时间戳', link: 'last-updated' },
{ text: '创建时间戳', link: 'created' },
{ text: '搜索', link: 'search' },
{ text: 'Carbon Ads', link: 'carbon-ads' }
]

@ -328,6 +328,46 @@ export interface LastUpdatedOptions {
}
```
## created
- 类型:`CreatedOptions`
允许自定义文档创建的文本和日期格式。
```ts
export default {
themeConfig: {
created: {
text: 'Created at',
formatOptions: {
dateStyle: 'full',
timeStyle: 'medium'
}
}
}
}
```
```ts
export interface CreatedOptions {
/**
* Set custom created text.
*
* @default 'Created'
*/
text?: string
/**
* Set options for created time formatting.
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat#using_options
*
* @default
* { dateStyle: 'short', timeStyle: 'short' }
*/
formatOptions?: Intl.DateTimeFormatOptions & { forceLocale?: boolean }
}
```
## algolia
- 类型:`AlgoliaSearch`

@ -0,0 +1,29 @@
# 创建于 {#created}
文档的创建时间会显示在页面右下角。要启用它,请将 `created` 选项添加到配置中。
::: tip
VitePress 通过每个文件第一次 Git 提交的时间戳显示"创建"时间,因此你必须提交 markdown 文件才能看到创建时间。
具体实现上VitePress 会对每个文件执行`git log -1 --pretty=%at --follow --diff-filter=A`命令以获取时间戳。若所有页面显示相同的创建时间,请参考[最后更新时间](./default-theme-last-updated)页面中的相同段落。
:::
## 全局配置 {#site-level-config}
```js
export default {
created: true
}
```
## frontmatter 配置 {#frontmatter-config}
可以使用 frontmatter 上的 `created` 选项单独禁用某个页面的最后更新展示:
```yaml
---
created: false
---
```
另请参阅[默认主题:创建时间](./default-theme-config#created) 了解更多详细信息。主题级别的任何 [truthy](https://developer.mozilla.org/zh-CN/docs/Glossary/Truthy) 值也将启用该功能,除非在站点或页面级别明确禁用。

@ -175,6 +175,19 @@ lastUpdated: false
---
```
### created
- 类型:`boolean | Date`
- 默认值:`true`
是否在当前页面的页脚中显示[创建时间](./default-theme-created)的文本。如果指定了日期时间,则会显示该日期时间而不是初次 git 提交的时间戳。
```yaml
---
created: false
---
```
### editLink
- 类型:`boolean`

@ -55,6 +55,7 @@ interface PageData {
params?: Record<string, any>
isNotFound?: boolean
lastUpdated?: number
created?: number
}
```

@ -512,6 +512,15 @@ export default {
使用默认主题时,启用此选项将显示每个页面的最后更新时间。可以通过 [`themeConfig.lastUpdatedText`](./default-theme-config#lastupdatedtext) 选项自定义文本。
### created
- 类型:`boolean`
- 默认值: `false`
是否使用 Git 获取每个页面的文档创建时间戳。时间戳将包含在每个页面的页面数据中,可通过 [`useData`](./runtime-api#usedata) 访问。
使用默认主题时,启用此选项将显示每个页面的文档创建时间。可以通过 [`themeConfig.created.text`](./default-theme-config#created) 选项自定义文本。
## 自定义 {#customization}
### markdown

@ -4,7 +4,7 @@ import { useData } from '../composables/data'
import { useEditLink } from '../composables/edit-link'
import { usePrevNext } from '../composables/prev-next'
import VPLink from './VPLink.vue'
import VPDocFooterLastUpdated from './VPDocFooterLastUpdated.vue'
import VPDocFooterTimestamp from './VPDocFooterTimestamp.vue'
const { theme, page, frontmatter } = useData()
@ -15,10 +15,12 @@ const hasEditLink = computed(
() => theme.value.editLink && frontmatter.value.editLink !== false
)
const hasLastUpdated = computed(() => page.value.lastUpdated)
const hasCreated = computed(() => page.value.created)
const showFooter = computed(
() =>
hasEditLink.value ||
hasLastUpdated.value ||
hasCreated.value ||
control.value.prev ||
control.value.next
)
@ -28,7 +30,7 @@ const showFooter = computed(
<footer v-if="showFooter" class="VPDocFooter">
<slot name="doc-footer-before" />
<div v-if="hasEditLink || hasLastUpdated" class="edit-info">
<div v-if="hasEditLink || hasLastUpdated || hasCreated" class="edit-info">
<div v-if="hasEditLink" class="edit-link">
<VPLink class="edit-link-button" :href="editLink.url" :no-icon="true">
<span class="vpi-square-pen edit-link-icon" />
@ -36,9 +38,12 @@ const showFooter = computed(
</VPLink>
</div>
<div v-if="hasLastUpdated" class="last-updated">
<VPDocFooterLastUpdated />
</div>
<table v-if="hasLastUpdated || hasCreated" class="VPTimestamps">
<tbody>
<VPDocFooterTimestamp :is-created="true" v-if="hasCreated" />
<VPDocFooterTimestamp :is-created="false" v-if="hasLastUpdated" />
</tbody>
</table>
</div>
<nav
@ -96,6 +101,10 @@ const showFooter = computed(
}
}
.edit-link {
align-self: flex-end;
}
.edit-link-button {
display: flex;
align-items: center;

@ -6,9 +6,13 @@ import { useData } from '../composables/data'
const { theme, page, lang: pageLang } = useData()
const { language: browserLang } = useNavigatorLanguage()
const props = defineProps<{
isCreated?: boolean
}>()
const timeRef = useTemplateRef('timeRef')
const date = computed(() => new Date(page.value.lastUpdated!))
const date = computed(() => new Date(props.isCreated ? page.value.created! : page.value.lastUpdated!))
const isoDatetime = computed(() => date.value.toISOString())
const datetime = shallowRef('')
@ -16,13 +20,17 @@ const datetime = shallowRef('')
// potential differences in timezones of the server and clients
onMounted(() => {
watchEffect(() => {
const lang = theme.value.lastUpdated?.formatOptions?.forceLocale
const formatOptions = props.isCreated
? theme.value.created?.formatOptions
: theme.value.lastUpdated?.formatOptions
const lang = formatOptions?.forceLocale
? pageLang.value
: browserLang.value
datetime.value = new Intl.DateTimeFormat(
lang,
theme.value.lastUpdated?.formatOptions ?? {
formatOptions ?? {
dateStyle: 'medium',
timeStyle: 'medium'
}
@ -38,14 +46,18 @@ onMounted(() => {
</script>
<template>
<p class="VPLastUpdated">
{{ theme.lastUpdated?.text || theme.lastUpdatedText || 'Last updated' }}:
<time ref="timeRef" :datetime="isoDatetime">{{ datetime }}</time>
</p>
<tr v-if="isCreated" class="VPCreated">
<td>{{ theme.created?.text || 'Created' }}:</td>
<td><time ref="timeRef" :datetime="isoDatetime">{{ datetime }}</time></td>
</tr>
<tr v-else class="VPLastUpdated">
<td>{{ theme.lastUpdated?.text || theme.lastUpdatedText || 'Last updated' }}:</td>
<td><time ref="timeRef" :datetime="isoDatetime">{{ datetime }}</time></td>
</tr>
</template>
<style scoped>
.VPLastUpdated {
.VPLastUpdated, .VPCreated {
line-height: 24px;
font-size: 14px;
font-weight: 500;
@ -53,10 +65,19 @@ onMounted(() => {
}
@media (min-width: 640px) {
.VPLastUpdated {
.VPLastUpdated, .VPCreated {
line-height: 32px;
font-size: 14px;
font-weight: 500;
}
}
td {
padding: 0;
}
td:first-child {
padding-right: 4px;
text-align: right;
}
</style>

@ -10,7 +10,7 @@ import {
} from 'sitemap'
import type { SiteConfig } from '../config'
import { slash } from '../shared'
import { getGitTimestamp } from '../utils/getGitTimestamp'
import { getGitLastUpdatedTimestamp } from '../utils/getGitTimestamp'
import { task } from '../utils/task'
export async function generateSitemap(siteConfig: SiteConfig) {
@ -30,7 +30,7 @@ export async function generateSitemap(siteConfig: SiteConfig) {
if (data.lastUpdated === false) return undefined
if (data.lastUpdated instanceof Date) return +data.lastUpdated
return (await getGitTimestamp(slash(file))) || undefined
return (await getGitLastUpdatedTimestamp(slash(file))) || undefined
}
await task('generating sitemap', async () => {

@ -143,6 +143,7 @@ export async function resolveConfig(
markdown: userConfig.markdown,
lastUpdated:
userConfig.lastUpdated ?? !!userConfig.themeConfig?.lastUpdated,
created: userConfig.created ?? !!userConfig.themeConfig?.created,
vue: userConfig.vue,
vite: userConfig.vite,
shouldPreload: userConfig.shouldPreload,

@ -19,7 +19,10 @@ import {
type MarkdownEnv,
type PageData
} from './shared'
import { getGitTimestamp } from './utils/getGitTimestamp'
import {
getGitLastUpdatedTimestamp,
getGitCreatedTimestamp
} from './utils/getGitTimestamp'
import { processIncludes } from './utils/processIncludes'
const debug = createDebug('vitepress:md')
@ -85,6 +88,7 @@ export async function createMarkdownToVueRenderFn(
options: MarkdownOptions = {},
base = '/',
includeLastUpdatedData = false,
includeCreatedData = false,
cleanUrls = false,
siteConfig: SiteConfig
) {
@ -227,7 +231,15 @@ export async function createMarkdownToVueRenderFn(
if (frontmatter.lastUpdated instanceof Date) {
pageData.lastUpdated = +frontmatter.lastUpdated
} else {
pageData.lastUpdated = await getGitTimestamp(fileOrig)
pageData.lastUpdated = await getGitLastUpdatedTimestamp(fileOrig)
}
}
if (includeCreatedData && frontmatter.created !== false) {
if (frontmatter.created instanceof Date) {
pageData.created = +frontmatter.created
} else {
pageData.created = await getGitCreatedTimestamp(fileOrig)
}
}

@ -31,7 +31,7 @@ import { staticDataPlugin } from './plugins/staticDataPlugin'
import { webFontsPlugin } from './plugins/webFontsPlugin'
import { slash, type PageDataPayload } from './shared'
import { deserializeFunctions, serializeFunctions } from './utils/fnSerialize'
import { cacheAllGitTimestamps } from './utils/getGitTimestamp'
import { cacheAllGitLastUpdatedTimestamps } from './utils/getGitTimestamp'
declare module 'vite' {
interface UserConfig {
@ -81,6 +81,7 @@ export async function createVitePressPlugin(
vue: userVuePluginOptions,
vite: userViteConfig,
lastUpdated,
created,
cleanUrls
} = siteConfig
@ -114,12 +115,13 @@ export async function createVitePressPlugin(
async configResolved(resolvedConfig) {
config = resolvedConfig
// pre-resolve git timestamps
if (lastUpdated) await cacheAllGitTimestamps(srcDir)
if (lastUpdated) await cacheAllGitLastUpdatedTimestamps(srcDir)
markdownToVue = await createMarkdownToVueRenderFn(
srcDir,
markdown,
config.base,
lastUpdated,
created,
cleanUrls,
siteConfig
)

@ -66,6 +66,7 @@ export interface UserConfig<
| 'force-auto'
| (Omit<UseDarkOptions, 'initialValue'> & { initialValue?: 'dark' })
lastUpdated?: boolean
created?: boolean
contentProps?: Record<string, any>
/**
@ -204,6 +205,7 @@ export interface SiteConfig<ThemeConfig = any> extends Pick<
| 'mpa'
| 'metaChunk'
| 'lastUpdated'
| 'created'
| 'ignoreDeadLinks'
| 'cleanUrls'
| 'useWebFonts'

@ -6,7 +6,8 @@ import { Transform, type TransformCallback } from 'node:stream'
import { slash } from '../shared'
const debug = createDebug('vitepress:git')
const cache = new Map<string, number>()
const lastUpdatedCache = new Map<string, number>()
const createdCache = new Map<string, number>()
const RS = 0x1e
const NUL = 0x00
@ -103,7 +104,7 @@ class GitLogParser extends Transform {
}
}
export async function cacheAllGitTimestamps(
export async function cacheAllGitLastUpdatedTimestamps(
root: string,
pathspec: string[] = ['*.md']
): Promise<void> {
@ -121,7 +122,7 @@ export async function cacheAllGitTimestamps(
]
return new Promise((resolve, reject) => {
cache.clear()
lastUpdatedCache.clear()
const child = spawn('git', args, { cwd: root })
child.stdout
@ -129,7 +130,8 @@ export async function cacheAllGitTimestamps(
.on('data', (rec: GitLogRecord) => {
for (const file of rec.files) {
const slashed = slash(path.resolve(gitRoot, file))
if (!cache.has(slashed)) cache.set(slashed, rec.ts)
if (!lastUpdatedCache.has(slashed))
lastUpdatedCache.set(slashed, rec.ts)
}
})
.on('error', reject)
@ -139,8 +141,10 @@ export async function cacheAllGitTimestamps(
})
}
export async function getGitTimestamp(file: string): Promise<number> {
const cached = cache.get(file)
export async function getGitLastUpdatedTimestamp(
file: string
): Promise<number> {
const cached = lastUpdatedCache.get(file)
if (cached) return cached
// most likely will never happen except for recently added files in dev
@ -162,7 +166,43 @@ export async function getGitTimestamp(file: string): Promise<number> {
const ts = Number.parseInt(output.trim(), 10) * 1000
if (!(ts > 0)) return resolve(0)
cache.set(file, ts)
lastUpdatedCache.set(file, ts)
resolve(ts)
})
child.on('error', reject)
})
}
export async function getGitCreatedTimestamp(file: string): Promise<number> {
const cached = createdCache.get(file)
if (cached) return cached
if (!fs.existsSync(file)) return 0
return new Promise((resolve, reject) => {
const child = spawn(
'git',
[
'log',
'-1',
'--pretty=%at',
'--follow',
'--diff-filter=A',
'--',
path.basename(file)
],
{ cwd: path.dirname(file) }
)
let output = ''
child.stdout.on('data', (d) => (output += String(d)))
child.on('close', () => {
const ts = Number.parseInt(output.trim(), 10) * 1000
if (!(ts > 0)) return resolve(0)
createdCache.set(file, ts)
resolve(ts)
})

@ -42,6 +42,7 @@ export const notFoundPageData: PageData = {
headers: [],
frontmatter: { sidebar: false, layout: 'page' },
lastUpdated: 0,
created: 0,
isNotFound: true
}

@ -73,6 +73,8 @@ export namespace DefaultTheme {
lastUpdated?: LastUpdatedOptions
created?: CreatedOptions
/**
* Set custom prev/next labels.
*/
@ -433,6 +435,26 @@ export namespace DefaultTheme {
formatOptions?: Intl.DateTimeFormatOptions & { forceLocale?: boolean }
}
// created --------------------------------------------------------------
export interface CreatedOptions {
/**
* Set custom created text.
*
* @default 'Created'
*/
text?: string
/**
* Set options for created time formatting.
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat#using_options
*
* @default
* { dateStyle: 'short', timeStyle: 'short' }
*/
formatOptions?: Intl.DateTimeFormatOptions & { forceLocale?: boolean }
}
// not found -----------------------------------------------------------------
export interface NotFoundOptions {

1
types/shared.d.ts vendored

@ -33,6 +33,7 @@ export interface PageData {
params?: Record<string, any>
isNotFound?: boolean
lastUpdated?: number
created?: number
}
/**

Loading…
Cancel
Save