@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>
@@ -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.10",
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.76",
31
- "@iconify-json/simple-icons": "^1.2.60",
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.8.2",
34
+ "@nuxt/content": "^3.9.0",
35
35
  "@nuxt/image": "^2.0.0",
36
- "@nuxt/kit": "^4.2.1",
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.1",
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
+ })