@movk/nuxt-docs 1.3.9 → 1.3.11

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.
@@ -25,6 +25,10 @@ const props = defineProps<{
25
25
  * The name of the component or file to get the changelog for.
26
26
  */
27
27
  name?: string
28
+ /**
29
+ * The author to filter commits by.
30
+ */
31
+ author?: string
28
32
  }>()
29
33
 
30
34
  const SHA_SHORT_LENGTH = 5
@@ -50,8 +54,8 @@ const filePath = computed(() => {
50
54
  })
51
55
 
52
56
  const { data: commits } = await useLazyFetch<Commit[]>('/api/github/commits', {
53
- key: `commit-changelog-${props.name ?? routeName.value}`,
54
- query: { path: filePath.value }
57
+ key: `commit-changelog-${props.name ?? routeName.value}-${props.author ?? 'all'}`,
58
+ query: { path: filePath.value, author: props.author }
55
59
  })
56
60
 
57
61
  // 格式化提交消息
@@ -83,11 +87,7 @@ const formattedCommits = computed(() => {
83
87
  <div v-else class="flex flex-col gap-1.5 relative">
84
88
  <div class="bg-accented w-px h-full absolute left-[11px] z-[-1]" />
85
89
 
86
- <div
87
- v-for="commit of formattedCommits"
88
- :key="commit.sha"
89
- class="flex gap-1.5 items-center"
90
- >
90
+ <div v-for="commit of formattedCommits" :key="commit.sha" class="flex gap-1.5 items-center">
91
91
  <div class="bg-accented ring-2 ring-bg size-1.5 mx-[8.5px] rounded-full" />
92
92
  <MDC :value="commit.formatted" class="text-sm *:py-0 *:my-0 [&_code]:text-xs" tag="div" />
93
93
  </div>
@@ -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>
@@ -26,12 +26,25 @@ declare module 'nuxt/schema' {
26
26
  }
27
27
  }
28
28
  github: {
29
+ owner: string
30
+ name: string
29
31
  url: string
30
32
  branch: string
31
33
  rootDir: string
32
34
  commitPath: string
33
35
  since: string
34
36
  suffix: string
37
+ per_page: number
38
+ until: string
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
+ }
35
48
  } | false
36
49
  }
37
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
@@ -52,7 +52,9 @@ export default defineNuxtModule({
52
52
  commitPath: 'src',
53
53
  suffix: 'vue',
54
54
  since: '2025-01-31T04:00:00Z',
55
- branch: getGitBranch()
55
+ branch: getGitBranch(),
56
+ per_page: 100,
57
+ until: new Date().toISOString()
56
58
  })
57
59
 
58
60
  const componentsPath = resolve('../app/components')
@@ -68,7 +70,7 @@ export default defineNuxtModule({
68
70
  'nuxt-og-image',
69
71
  '@nuxtjs/plausible',
70
72
  '@nuxt/ui',
71
- 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$)`)
72
74
  ],
73
75
  metaFields: {
74
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.9",
4
+ "version": "1.3.11",
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,9 +27,9 @@
27
27
  "README.md"
28
28
  ],
29
29
  "dependencies": {
30
- "@iconify-json/lucide": "^1.2.75",
31
- "@iconify-json/simple-icons": "^1.2.60",
32
- "@iconify-json/vscode-icons": "^1.2.36",
30
+ "@iconify-json/lucide": "^1.2.77",
31
+ "@iconify-json/simple-icons": "^1.2.61",
32
+ "@iconify-json/vscode-icons": "^1.2.37",
33
33
  "@movk/core": "^1.0.2",
34
34
  "@nuxt/content": "^3.8.2",
35
35
  "@nuxt/image": "^2.0.0",
@@ -37,18 +37,18 @@
37
37
  "@nuxt/ui": "^4.2.1",
38
38
  "@nuxtjs/seo": "^3.2.2",
39
39
  "@octokit/rest": "^22.0.1",
40
- "@vueuse/core": "^14.0.0",
41
- "@vueuse/nuxt": "^14.0.0",
40
+ "@vueuse/core": "^14.1.0",
41
+ "@vueuse/nuxt": "^14.1.0",
42
42
  "defu": "^6.1.4",
43
43
  "exsolve": "^1.0.8",
44
44
  "git-url-parse": "^16.1.0",
45
45
  "motion-v": "^1.7.4",
46
- "nuxt-component-meta": "^0.14.2",
46
+ "nuxt-component-meta": "^0.15.0",
47
47
  "nuxt-llms": "^0.1.3",
48
48
  "ohash": "^2.0.11",
49
49
  "pathe": "^2.0.3",
50
50
  "pkg-types": "^2.3.0",
51
- "prettier": "^3.6.2",
51
+ "prettier": "^3.7.3",
52
52
  "scule": "^1.3.0",
53
53
  "tailwindcss": "^4.1.17",
54
54
  "ufo": "^1.6.1"
@@ -5,7 +5,7 @@ export default defineCachedEventHandler(async (event) => {
5
5
  return []
6
6
  }
7
7
 
8
- const { path } = getQuery(event) as { path: string }
8
+ const { path, author } = getQuery(event) as { path: string, author?: string }
9
9
  if (!path) {
10
10
  throw createError({
11
11
  statusCode: 400,
@@ -16,10 +16,14 @@ export default defineCachedEventHandler(async (event) => {
16
16
  const { github } = useAppConfig()
17
17
  const octokit = new Octokit({ auth: process.env.NUXT_GITHUB_TOKEN })
18
18
  const commits = await octokit.paginate(octokit.rest.repos.listCommits, {
19
+ sha: github.branch,
19
20
  owner: github.owner,
20
21
  repo: github.name,
21
22
  path,
22
- since: github.since
23
+ since: github.since,
24
+ per_page: github.per_page,
25
+ until: github.until,
26
+ ...(author && { author })
23
27
  })
24
28
 
25
29
  return commits.map(commit => ({
@@ -29,5 +33,8 @@ export default defineCachedEventHandler(async (event) => {
29
33
  }))
30
34
  }, {
31
35
  maxAge: 60 * 60,
32
- getKey: event => `commits-${getQuery(event).path}`
36
+ getKey: (event) => {
37
+ const { path, author } = getQuery(event)
38
+ return `commits-${path}${author ? `-${author}` : ''}`
39
+ }
33
40
  })
@@ -0,0 +1,98 @@
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 formatOptions: Intl.DateTimeFormatOptions = dateFormat.options ?? {
67
+ year: 'numeric',
68
+ month: 'numeric',
69
+ day: 'numeric',
70
+ hour: '2-digit',
71
+ minute: '2-digit'
72
+ }
73
+ const dateFormatted = date
74
+ ? new Date(date).toLocaleDateString(locale, formatOptions)
75
+ : ''
76
+
77
+ return {
78
+ sha: commit.sha,
79
+ date,
80
+ dateFormatted,
81
+ message: commit.commit.message?.split('\n')[0] ?? '',
82
+ url: commitUrl,
83
+ author: {
84
+ name: authorName,
85
+ login: authorLogin,
86
+ avatar: authorAvatar
87
+ }
88
+ }
89
+ } catch {
90
+ return null
91
+ }
92
+ }, {
93
+ maxAge: 60 * 60, // 缓存 1 小时
94
+ getKey: (event) => {
95
+ const { path } = getQuery(event)
96
+ return `last-commit-${path}`
97
+ }
98
+ })