@movk/nuxt-docs 1.3.10 → 1.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
interface LastCommit {
|
|
3
|
+
sha: string
|
|
4
|
+
date: string
|
|
5
|
+
dateFormatted: string
|
|
6
|
+
message: string
|
|
7
|
+
url: string
|
|
8
|
+
author: {
|
|
9
|
+
name: string
|
|
10
|
+
login: string
|
|
11
|
+
avatar: string
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const props = withDefaults(defineProps<{
|
|
16
|
+
/**
|
|
17
|
+
* 文件路径(不含扩展名),通常由页面自动传入
|
|
18
|
+
*/
|
|
19
|
+
stem: string
|
|
20
|
+
/**
|
|
21
|
+
* 文件扩展名,通常由页面自动传入
|
|
22
|
+
*/
|
|
23
|
+
extension: string
|
|
24
|
+
/**
|
|
25
|
+
* 是否显示提交信息。
|
|
26
|
+
* @defaultValue true
|
|
27
|
+
*/
|
|
28
|
+
showMessage?: boolean
|
|
29
|
+
/**
|
|
30
|
+
* 是否显示作者头像。
|
|
31
|
+
* @defaultValue true
|
|
32
|
+
*/
|
|
33
|
+
showAvatar?: boolean
|
|
34
|
+
}>(), {
|
|
35
|
+
showMessage: true,
|
|
36
|
+
showAvatar: true
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const { github } = useAppConfig()
|
|
40
|
+
|
|
41
|
+
const filePath = computed(() => {
|
|
42
|
+
if (!props.stem || !props.extension) return ''
|
|
43
|
+
|
|
44
|
+
const rootDir = github && typeof github === 'object' ? github.rootDir : ''
|
|
45
|
+
return [rootDir, 'content', `${props.stem}.${props.extension}`].filter(Boolean).join('/')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const { data: commit } = await useFetch<LastCommit | null>('/api/github/last-commit', {
|
|
49
|
+
key: `last-commit-${filePath.value}`,
|
|
50
|
+
query: { path: filePath },
|
|
51
|
+
default: () => null,
|
|
52
|
+
lazy: true,
|
|
53
|
+
server: false
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const commitUrl = computed(() => commit.value?.url ?? '')
|
|
57
|
+
|
|
58
|
+
const authorUrl = computed(() => {
|
|
59
|
+
const login = commit.value?.author.login
|
|
60
|
+
return login ? `https://github.com/${login}` : ''
|
|
61
|
+
})
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<template>
|
|
65
|
+
<div v-if="commit" class="flex items-center flex-wrap gap-1.5 text-sm text-muted mt-2">
|
|
66
|
+
<span class="text-dimmed">最后更新于</span>
|
|
67
|
+
<time class="font-medium text-default" :datetime="commit.date">{{ commit.dateFormatted }}</time>
|
|
68
|
+
<span class="text-dimmed">由</span>
|
|
69
|
+
<ULink
|
|
70
|
+
v-if="authorUrl"
|
|
71
|
+
:to="authorUrl"
|
|
72
|
+
target="_blank"
|
|
73
|
+
class="inline-flex items-center gap-1.5 hover:opacity-80 transition-opacity"
|
|
74
|
+
>
|
|
75
|
+
<UAvatar
|
|
76
|
+
v-if="showAvatar && commit.author.avatar"
|
|
77
|
+
:src="commit.author.avatar"
|
|
78
|
+
:alt="commit.author.name"
|
|
79
|
+
size="2xs"
|
|
80
|
+
/>
|
|
81
|
+
<UBadge color="neutral" variant="outline" size="sm">
|
|
82
|
+
{{ commit.author.name || commit.author.login }}
|
|
83
|
+
</UBadge>
|
|
84
|
+
</ULink>
|
|
85
|
+
<span v-else class="inline-flex items-center gap-1.5">
|
|
86
|
+
<UAvatar
|
|
87
|
+
v-if="showAvatar && commit.author.avatar"
|
|
88
|
+
:src="commit.author.avatar"
|
|
89
|
+
:alt="commit.author.name"
|
|
90
|
+
size="2xs"
|
|
91
|
+
/>
|
|
92
|
+
<UBadge color="neutral" variant="outline" size="sm">
|
|
93
|
+
{{ commit.author.name || commit.author.login }}
|
|
94
|
+
</UBadge>
|
|
95
|
+
</span>
|
|
96
|
+
<template v-if="showMessage && commit.message">
|
|
97
|
+
<span class="text-dimmed">提交</span>
|
|
98
|
+
<ULink
|
|
99
|
+
v-if="commitUrl"
|
|
100
|
+
:to="commitUrl"
|
|
101
|
+
target="_blank"
|
|
102
|
+
class="hover:opacity-80 transition-opacity max-w-[250px]"
|
|
103
|
+
>
|
|
104
|
+
<UBadge
|
|
105
|
+
color="neutral"
|
|
106
|
+
variant="outline"
|
|
107
|
+
size="sm"
|
|
108
|
+
class="font-mono text-xs"
|
|
109
|
+
>
|
|
110
|
+
<span class="truncate">{{ commit.message }}</span>
|
|
111
|
+
</UBadge>
|
|
112
|
+
</ULink>
|
|
113
|
+
<UBadge
|
|
114
|
+
v-else
|
|
115
|
+
color="neutral"
|
|
116
|
+
variant="outline"
|
|
117
|
+
size="sm"
|
|
118
|
+
class="max-w-[250px] font-mono text-xs"
|
|
119
|
+
>
|
|
120
|
+
<span class="truncate">{{ commit.message }}</span>
|
|
121
|
+
</UBadge>
|
|
122
|
+
</template>
|
|
123
|
+
</div>
|
|
124
|
+
</template>
|
|
@@ -106,6 +106,8 @@ defineOgImageComponent('Nuxt', {
|
|
|
106
106
|
unwrap="p"
|
|
107
107
|
:cache-key="`${kebabCase(route.path)}-description`"
|
|
108
108
|
/>
|
|
109
|
+
|
|
110
|
+
<PageLastCommit v-if="github && page?.stem && page?.extension" :stem="page.stem" :extension="page.extension" />
|
|
109
111
|
</template>
|
|
110
112
|
|
|
111
113
|
<template #links>
|
package/app/types/index.d.ts
CHANGED
|
@@ -37,6 +37,14 @@ declare module 'nuxt/schema' {
|
|
|
37
37
|
per_page: number
|
|
38
38
|
until: string
|
|
39
39
|
author?: string
|
|
40
|
+
/**
|
|
41
|
+
* 日期格式化配置
|
|
42
|
+
* @example { locale: 'zh-CN', options: { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' } }
|
|
43
|
+
*/
|
|
44
|
+
dateFormat?: {
|
|
45
|
+
locale?: string
|
|
46
|
+
options?: Intl.DateTimeFormatOptions
|
|
47
|
+
}
|
|
40
48
|
} | false
|
|
41
49
|
}
|
|
42
50
|
}
|
package/content.config.ts
CHANGED
|
@@ -22,7 +22,7 @@ export default defineContentConfig({
|
|
|
22
22
|
include: 'docs/**/*'
|
|
23
23
|
}],
|
|
24
24
|
schema: z.object({
|
|
25
|
-
links: property(z.object({})).inherit('@nuxt/ui/components/Button.vue'),
|
|
25
|
+
links: z.array(property(z.object({})).inherit('@nuxt/ui/components/Button.vue')),
|
|
26
26
|
category: z.string().optional(),
|
|
27
27
|
navigation: z.object({
|
|
28
28
|
title: z.string().optional()
|
package/modules/config.ts
CHANGED
|
@@ -70,7 +70,7 @@ export default defineNuxtModule({
|
|
|
70
70
|
'nuxt-og-image',
|
|
71
71
|
'@nuxtjs/plausible',
|
|
72
72
|
'@nuxt/ui',
|
|
73
|
-
new RegExp(`${componentsPath.replace(/[/\\]/g, '[/\\\\]')}/(?!content/(ComponentEmits|ComponentProps|ComponentSlots|ComponentExample|CommitChangelog)\\.vue$)`)
|
|
73
|
+
new RegExp(`${componentsPath.replace(/[/\\]/g, '[/\\\\]')}/(?!content/(ComponentEmits|ComponentProps|ComponentSlots|ComponentExample|CommitChangelog|PageLastCommit)\\.vue$)`)
|
|
74
74
|
],
|
|
75
75
|
metaFields: {
|
|
76
76
|
type: false,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@movk/nuxt-docs",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.12",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "An elegant documentation theme for Nuxt, powered by Nuxt UI and Nuxt Content.",
|
|
7
7
|
"author": "YiXuan <mhaibaraai@gmail.com>",
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
"README.md"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@iconify-json/lucide": "^1.2.
|
|
31
|
-
"@iconify-json/simple-icons": "^1.2.
|
|
30
|
+
"@iconify-json/lucide": "^1.2.79",
|
|
31
|
+
"@iconify-json/simple-icons": "^1.2.62",
|
|
32
32
|
"@iconify-json/vscode-icons": "^1.2.37",
|
|
33
33
|
"@movk/core": "^1.0.2",
|
|
34
|
-
"@nuxt/content": "^3.
|
|
34
|
+
"@nuxt/content": "^3.9.0",
|
|
35
35
|
"@nuxt/image": "^2.0.0",
|
|
36
|
-
"@nuxt/kit": "^4.2.
|
|
36
|
+
"@nuxt/kit": "^4.2.2",
|
|
37
37
|
"@nuxt/ui": "^4.2.1",
|
|
38
38
|
"@nuxtjs/seo": "^3.2.2",
|
|
39
39
|
"@octokit/rest": "^22.0.1",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"ohash": "^2.0.11",
|
|
49
49
|
"pathe": "^2.0.3",
|
|
50
50
|
"pkg-types": "^2.3.0",
|
|
51
|
-
"prettier": "^3.7.
|
|
51
|
+
"prettier": "^3.7.4",
|
|
52
52
|
"scule": "^1.3.0",
|
|
53
53
|
"tailwindcss": "^4.1.17",
|
|
54
54
|
"ufo": "^1.6.1"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Octokit } from '@octokit/rest'
|
|
2
|
+
|
|
3
|
+
export default defineCachedEventHandler(async (event) => {
|
|
4
|
+
if (!process.env.NUXT_GITHUB_TOKEN) {
|
|
5
|
+
return null
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const { path } = getQuery(event) as { path: string }
|
|
9
|
+
if (!path) {
|
|
10
|
+
throw createError({
|
|
11
|
+
statusCode: 400,
|
|
12
|
+
statusMessage: 'Path is required'
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { github } = useAppConfig()
|
|
17
|
+
const octokit = new Octokit({ auth: process.env.NUXT_GITHUB_TOKEN })
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const { data: commits } = await octokit.rest.repos.listCommits({
|
|
21
|
+
sha: github.branch,
|
|
22
|
+
owner: github.owner,
|
|
23
|
+
repo: github.name,
|
|
24
|
+
path,
|
|
25
|
+
per_page: 1
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
if (!commits.length) {
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const commit = commits[0]
|
|
33
|
+
|
|
34
|
+
// 获取提交者信息,处理 web-flow 场景(PR squash merge)
|
|
35
|
+
let authorName = commit.commit.author?.name ?? ''
|
|
36
|
+
let authorLogin = commit.author?.login ?? ''
|
|
37
|
+
let authorAvatar = commit.author?.avatar_url ?? ''
|
|
38
|
+
let commitUrl = commit.html_url
|
|
39
|
+
|
|
40
|
+
// 如果提交者是 web-flow,尝试获取实际的 PR 作者
|
|
41
|
+
if (authorLogin === 'web-flow') {
|
|
42
|
+
try {
|
|
43
|
+
// 从 squash commit message 中提取 PR 编号 (#166)
|
|
44
|
+
const prMatch = commit.commit.message.match(/#(\d+)/)
|
|
45
|
+
if (prMatch?.[1]) {
|
|
46
|
+
const { data: prData } = await octokit.rest.pulls.get({
|
|
47
|
+
owner: github.owner,
|
|
48
|
+
repo: github.name,
|
|
49
|
+
pull_number: Number.parseInt(prMatch[1])
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
authorLogin = prData.user?.login ?? authorLogin
|
|
53
|
+
authorAvatar = prData.user?.avatar_url ?? authorAvatar
|
|
54
|
+
authorName = prData.user?.name || authorLogin
|
|
55
|
+
commitUrl = prData.html_url
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// 获取 PR 信息失败时忽略,使用原始提交者信息
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 格式化日期
|
|
63
|
+
const date = commit.commit.author?.date ?? ''
|
|
64
|
+
const dateFormat = github.dateFormat ?? {}
|
|
65
|
+
const locale = dateFormat.locale ?? 'zh-CN'
|
|
66
|
+
const timeZone = dateFormat.timeZone ?? 'Asia/Shanghai'
|
|
67
|
+
const formatOptions: Intl.DateTimeFormatOptions = dateFormat.options ?? {
|
|
68
|
+
year: 'numeric',
|
|
69
|
+
month: 'numeric',
|
|
70
|
+
day: 'numeric',
|
|
71
|
+
hour: '2-digit',
|
|
72
|
+
minute: '2-digit',
|
|
73
|
+
timeZone
|
|
74
|
+
}
|
|
75
|
+
const dateFormatted = date
|
|
76
|
+
? new Date(date).toLocaleDateString(locale, formatOptions)
|
|
77
|
+
: ''
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
sha: commit.sha,
|
|
81
|
+
date,
|
|
82
|
+
dateFormatted,
|
|
83
|
+
message: commit.commit.message?.split('\n')[0] ?? '',
|
|
84
|
+
url: commitUrl,
|
|
85
|
+
author: {
|
|
86
|
+
name: authorName,
|
|
87
|
+
login: authorLogin,
|
|
88
|
+
avatar: authorAvatar
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
}, {
|
|
95
|
+
maxAge: 60 * 60, // 缓存 1 小时
|
|
96
|
+
getKey: (event) => {
|
|
97
|
+
const { path } = getQuery(event)
|
|
98
|
+
return `last-commit-${path}`
|
|
99
|
+
}
|
|
100
|
+
})
|