@movk/nuxt-docs 1.13.0 → 1.13.1

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.
@@ -1,11 +1,27 @@
1
1
  <script setup lang="ts">
2
- import { camelCase, kebabCase, upperFirst } from '@movk/core'
2
+ import { camelCase, kebabCase, upperFirst } from 'scule'
3
3
 
4
4
  interface Commit {
5
5
  sha: string
6
+ date: string
6
7
  message: string
7
8
  }
8
9
 
10
+ interface Release {
11
+ tag_name: string
12
+ published_at: string
13
+ html_url: string
14
+ }
15
+
16
+ interface ReleaseGroup {
17
+ tag: string
18
+ url?: string
19
+ icon?: string
20
+ title: string
21
+ commits: Commit[]
22
+ published_at?: string
23
+ }
24
+
9
25
  const props = defineProps<{
10
26
  /**
11
27
  * 仓库中的文件路径
@@ -45,7 +61,6 @@ const SHA_SHORT_LENGTH = 5
45
61
  const { github } = useAppConfig()
46
62
  const route = useRoute()
47
63
 
48
- // 计算文件路径相关的值
49
64
  const routeName = computed(() => route.path.split('/').pop() ?? '')
50
65
  const githubUrl = computed(() => (github && typeof github === 'object' ? github.url : ''))
51
66
 
@@ -55,7 +70,6 @@ const filePath = computed(() => {
55
70
  const fileExtension = props.suffix ?? (github && typeof github === 'object' ? github.suffix : 'vue')
56
71
  const fileName = props.name ?? routeName.value
57
72
 
58
- // 根据 casing 参数转换文件名
59
73
  const transformedName = (() => {
60
74
  const casing = props.casing ?? (github && typeof github === 'object' ? github.casing : undefined) ?? 'auto'
61
75
 
@@ -82,38 +96,101 @@ const { data: commits } = await useLazyFetch<Commit[]>('/api/github/commits', {
82
96
  query: { path: [filePath.value], author: props.author }
83
97
  })
84
98
 
85
- // 格式化提交消息
86
- const formattedCommits = computed(() => {
99
+ const { data: releases } = await useLazyFetch<Release[]>('/api/github/releases.json')
100
+
101
+ const groupedByRelease = computed<ReleaseGroup[]>(() => {
87
102
  if (!commits.value?.length) return []
88
103
 
89
- return commits.value.map((commit) => {
90
- const shortSha = commit.sha.slice(0, SHA_SHORT_LENGTH)
91
- const commitLink = `[\`${shortSha}\`](${githubUrl.value}/commit/${commit.sha})`
104
+ const sortedReleases = (releases.value ?? [])
105
+ .filter(r => r.published_at)
106
+ .sort((a, b) => new Date(b.published_at).getTime() - new Date(a.published_at).getTime())
107
+
108
+ const releasesOldestFirst = [...sortedReleases].reverse()
109
+ const groups: ReleaseGroup[] = []
110
+ const unreleased: Commit[] = []
111
+
112
+ for (const commit of commits.value) {
113
+ const commitDate = new Date(commit.date).getTime()
114
+ const release = releasesOldestFirst.find(r => new Date(r.published_at).getTime() >= commitDate)
115
+
116
+ if (release) {
117
+ const majorTag = release.tag_name.replace(/-(alpha|beta|rc)\.\d+$/, '')
118
+ let group = groups.find(g => g.tag === majorTag)
119
+ if (!group) {
120
+ group = { tag: majorTag, title: majorTag, icon: 'i-lucide-tag', published_at: release.published_at, url: release.html_url, commits: [] }
121
+ groups.push(group)
122
+ }
123
+ if (new Date(release.published_at) > new Date(group.published_at!)) {
124
+ group.published_at = release.published_at
125
+ group.url = release.html_url
126
+ }
127
+ group.commits.push(commit)
128
+ } else {
129
+ unreleased.push(commit)
130
+ }
131
+ }
92
132
 
93
- const content = commit.message
94
- .replace(/\(.*?\)/, '')
95
- .replace(/#(\d+)/g, `<a href='${githubUrl.value}/issues/$1'>#$1</a>`)
96
- .replace(/`(.*?)`/g, '<code class="text-xs">$1</code>')
133
+ const result: ReleaseGroup[] = []
134
+ if (unreleased.length) {
135
+ result.push({ tag: 'unreleased', title: 'Soon', icon: 'i-lucide-tag', commits: unreleased })
136
+ }
97
137
 
98
- return {
99
- sha: commit.sha,
100
- formatted: `${commitLink} — ${content}`
101
- }
102
- })
138
+ const uniqueTags = [...new Set(sortedReleases.map(r => r.tag_name.replace(/-(alpha|beta|rc)\.\d+$/, '')))]
139
+ groups.sort((a, b) => uniqueTags.indexOf(a.tag) - uniqueTags.indexOf(b.tag))
140
+ result.push(...groups)
141
+
142
+ return result
103
143
  })
144
+
145
+ function normalizeCommitMessage(commit: Commit) {
146
+ const prefix = `[\`${commit.sha.slice(0, SHA_SHORT_LENGTH)}\`](${githubUrl.value}/commit/${commit.sha})`
147
+ const content = commit.message
148
+ .replace(/#(\d+)/g, `<a href='${githubUrl.value}/issues/$1'>#$1</a>`)
149
+ .replace(/`(.*?)`/g, '<code class="text-xs">$1</code>')
150
+
151
+ return `${prefix} — ${content}`
152
+ }
104
153
  </script>
105
154
 
106
155
  <template>
107
- <div v-if="!formattedCommits.length">
156
+ <div v-if="!commits?.length">
108
157
  No recent changes
109
158
  </div>
110
159
 
111
- <div v-else class="flex flex-col gap-1.5 relative">
112
- <div class="bg-accented w-px h-full absolute left-[11px] z-[-1]" />
113
-
114
- <div v-for="commit of formattedCommits" :key="commit.sha" class="flex gap-1.5 items-center">
115
- <div class="bg-accented ring-2 ring-bg size-1.5 mx-[8.5px] rounded-full" />
116
- <MDC :value="commit.formatted" class="text-sm *:py-0 *:my-0 [&_code]:text-xs" tag="div" />
117
- </div>
118
- </div>
160
+ <UTimeline
161
+ v-else
162
+ :items="groupedByRelease"
163
+ size="xs"
164
+ :ui="{ root: '', wrapper: 'mt-0 pb-0', title: 'mb-1.5 flex items-center justify-between' }"
165
+ >
166
+ <template #title="{ item }">
167
+ <UBadge
168
+ v-if="item.tag === 'unreleased'"
169
+ color="neutral"
170
+ variant="subtle"
171
+ :label="item.title"
172
+ class="w-12.5 justify-center"
173
+ />
174
+ <NuxtLink
175
+ v-else
176
+ :to="item.url"
177
+ target="_blank"
178
+ class="hover:underline"
179
+ >
180
+ <UBadge variant="subtle" :label="item.tag" />
181
+ </NuxtLink>
182
+
183
+ <time v-if="item.published_at" :datetime="item.published_at" class="text-xs text-dimmed font-normal">
184
+ {{ useTimeAgo(new Date(item.published_at)) }}
185
+ </time>
186
+ </template>
187
+
188
+ <template #description="{ item }">
189
+ <ul class="flex flex-col gap-1.5">
190
+ <li v-for="commit of item.commits" :key="commit.sha">
191
+ <MDC :value="normalizeCommitMessage(commit)" class="text-sm [&_code]:text-xs" unwrap="p" />
192
+ </li>
193
+ </ul>
194
+ </template>
195
+ </UTimeline>
119
196
  </template>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@movk/nuxt-docs",
3
3
  "type": "module",
4
- "version": "1.13.0",
4
+ "version": "1.13.1",
5
5
  "private": false,
6
6
  "description": "Modern Nuxt 4 documentation theme with auto-generated component docs, AI chat assistant, MCP server, and complete developer experience optimization.",
7
7
  "author": "YiXuan <mhaibaraai@gmail.com>",
@@ -0,0 +1,28 @@
1
+ import { Octokit } from '@octokit/rest'
2
+
3
+ export default defineCachedEventHandler(async () => {
4
+ if (!process.env.NUXT_GITHUB_TOKEN) {
5
+ return []
6
+ }
7
+
8
+ const { github } = useAppConfig()
9
+
10
+ if (!github || typeof github === 'boolean') {
11
+ throw createError({
12
+ status: 500,
13
+ statusText: 'GitHub configuration is not available'
14
+ })
15
+ }
16
+
17
+ const octokit = new Octokit({ auth: process.env.NUXT_GITHUB_TOKEN })
18
+
19
+ const { data: releases } = await octokit.rest.repos.listReleases({
20
+ owner: github.owner,
21
+ repo: github.name
22
+ })
23
+
24
+ return releases
25
+ }, {
26
+ maxAge: 60 * 60,
27
+ getKey: () => 'releases'
28
+ })