Skip to content

Commit 242acb3

Browse files
authored
feat: add the new package timeline page to the command palette (#2635)
1 parent 89d754f commit 242acb3

10 files changed

Lines changed: 119 additions & 35 deletions

File tree

app/components/Package/Header.vue

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -164,15 +164,7 @@ const diffLink = computed((): RouteLocationRaw | null => {
164164
165165
const timelineLink = computed((): RouteLocationRaw | null => {
166166
if (props.pkg == null || props.resolvedVersion == null) return null
167-
const split = props.pkg.name.split('/')
168-
return {
169-
name: 'timeline',
170-
params: {
171-
org: split.length === 2 ? split[0] : undefined,
172-
packageName: split.length === 2 ? split[1]! : split[0]!,
173-
version: props.resolvedVersion,
174-
},
175-
}
167+
return packageTimelineRoute(props.pkg.name, props.resolvedVersion)
176168
})
177169
178170
useShortcuts({

app/composables/useCommandPalettePackageCommands.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,20 @@ export function useCommandPalettePackageCommands(
8888
activeLabel: activeLabel(route.name === 'code', t('command_palette.here')),
8989
to: codeLink,
9090
},
91+
{
92+
id: 'package-timeline',
93+
group: 'package',
94+
label: t('package.links.timeline'),
95+
keywords: [
96+
resolvedContext.packageName,
97+
t('shortcuts.open_timeline'),
98+
t('package.links.timeline'),
99+
],
100+
iconClass: 'i-lucide:history',
101+
active: route.name === 'timeline',
102+
activeLabel: activeLabel(route.name === 'timeline', t('command_palette.here')),
103+
to: packageTimelineRoute(resolvedContext.packageName, resolvedContext.resolvedVersion),
104+
},
91105
{
92106
id: 'package-compare',
93107
group: 'package',

app/composables/useCommandPaletteVersionCommands.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
// @unocss-include
2-
import type { MaybeRefOrGetter } from 'vue'
2+
import type { MaybeRef, MaybeRefOrGetter } from 'vue'
3+
import type { RouteLocationRaw } from 'vue-router'
34
import type {
45
CommandPaletteCommand,
56
CommandPaletteContextCommandInput,
67
CommandPalettePackageContext,
78
} from '~/types/command-palette'
89
import { compare, satisfies, validRange } from 'semver'
910

11+
type CommandPaletteVersionRoute = (version: string) => RouteLocationRaw
12+
1013
function getSortedVersions(context: CommandPalettePackageContext) {
1114
return [...context.versions].sort((a, b) => {
1215
if (a === context.resolvedVersion) return -1
@@ -19,7 +22,7 @@ function createVersionCommands(
1922
context: CommandPalettePackageContext,
2023
t: ReturnType<typeof useI18n>['t'],
2124
versions = getSortedVersions(context),
22-
urlPattern?: string | null,
25+
routeForVersion?: CommandPaletteVersionRoute | null,
2326
): CommandPaletteCommand[] {
2427
return versions.map(version => ({
2528
id: `version:${version}`,
@@ -28,13 +31,13 @@ function createVersionCommands(
2831
keywords: [context.packageName, version, t('command_palette.groups.versions')],
2932
iconClass: 'i-lucide:tag',
3033
active: version === context.resolvedVersion,
31-
to: urlPattern?.replace('{version}', version) ?? packageRoute(context.packageName, version),
34+
to: routeForVersion?.(version) ?? packageRoute(context.packageName, version),
3235
}))
3336
}
3437

3538
export function useCommandPaletteVersionCommands(
3639
context: MaybeRefOrGetter<CommandPalettePackageContext | null>,
37-
urlPattern?: MaybeRefOrGetter<string | null | undefined>,
40+
routeForVersion?: MaybeRef<CommandPaletteVersionRoute | null | undefined>,
3841
) {
3942
const { t } = useI18n()
4043

@@ -43,7 +46,7 @@ export function useCommandPaletteVersionCommands(
4346
const resolvedContext = toValue(context)
4447
if (!resolvedContext?.resolvedVersion) return []
4548

46-
return createVersionCommands(resolvedContext, t, undefined, toValue(urlPattern))
49+
return createVersionCommands(resolvedContext, t, undefined, unref(routeForVersion))
4750
}),
4851
)
4952

@@ -58,6 +61,6 @@ export function useCommandPaletteVersionCommands(
5861
satisfies(version, semverRange, { includePrerelease: true }),
5962
)
6063

61-
return createVersionCommands(resolvedContext, t, matchingVersions, toValue(urlPattern))
64+
return createVersionCommands(resolvedContext, t, matchingVersions, unref(routeForVersion))
6265
})
6366
}

app/pages/diff/[[org]]/[packageName]/v/[versionRange].vue

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -116,19 +116,19 @@ const latestVersionDetailed = computed(() => {
116116
return pkg.value.versions[latestTag] ?? null
117117
})
118118
119-
const normalizeRoutePath = (routeLocation: RouteLocationRaw) => {
120-
const resolvedHref = router.resolve(routeLocation).href
121-
return resolvedHref.replace(/%7B/g, '{').replace(/%7D/g, '}')
119+
function diffVersionUrlPattern(from: string, to: string) {
120+
const { org, packageName: name } = route.params
121+
return `/diff/${org ? `${org}/` : ''}${name}/v/${from}...${to}`
122122
}
123123
124-
const fromVersionUrlPattern = computed(() => {
125-
return normalizeRoutePath(diffRoute(packageName.value, '{version}', toVersion.value))
126-
})
127-
const toVersionUrlPattern = computed(() => {
128-
return normalizeRoutePath(diffRoute(packageName.value, fromVersion.value, '{version}'))
129-
})
124+
const fromVersionUrlPattern = computed(() => diffVersionUrlPattern('{version}', toVersion.value))
125+
const toVersionUrlPattern = computed(() => diffVersionUrlPattern(fromVersion.value, '{version}'))
126+
127+
function fromVersionRoute(version: string): RouteLocationRaw {
128+
return diffRoute(packageName.value, version, toVersion.value)
129+
}
130130
131-
useCommandPaletteVersionCommands(commandPalettePackageContext, fromVersionUrlPattern)
131+
useCommandPaletteVersionCommands(commandPalettePackageContext, fromVersionRoute)
132132
133133
useSeoMeta({
134134
title: () => {

app/pages/package-code/[[org]]/[packageName]/v/[version]/[...filePath].vue

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script setup lang="ts">
2+
import type { RouteLocationRaw } from 'vue-router'
23
import type { CommandPaletteContextCommandInput } from '~/types/command-palette'
34
45
// Maximum file size we'll try to load (500KB) - must match server
@@ -86,7 +87,16 @@ const versionUrlPattern = computed(() =>
8687
}),
8788
)
8889
89-
useCommandPaletteVersionCommands(commandPalettePackageContext, versionUrlPattern)
90+
function codeVersionRoute(nextVersion: string): RouteLocationRaw {
91+
return getCodeUrl({
92+
org: route.params.org,
93+
packageName: route.params.packageName,
94+
version: nextVersion,
95+
filePath: filePath.value,
96+
})
97+
}
98+
99+
useCommandPaletteVersionCommands(commandPalettePackageContext, codeVersionRoute)
90100
91101
// Fetch file tree
92102
const { data: fileTree, status: treeStatus } = useFetch<PackageFileTreeResponse>(

app/pages/package-docs/[...path].vue

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script setup lang="ts">
2+
import type { RouteLocationRaw } from 'vue-router'
23
import { setResponseHeader } from 'h3'
34
45
definePageMeta({
@@ -123,7 +124,19 @@ const versionUrlPattern = computed(
123124
() => `/package-docs/${pkg.value?.name || packageName.value}/v/{version}`,
124125
)
125126
126-
useCommandPaletteVersionCommands(commandPalettePackageContext, versionUrlPattern)
127+
function docsVersionRoute(version: string): RouteLocationRaw {
128+
const name = pkg.value?.name || packageName.value
129+
const [firstSegment = name, ...remainingSegments] = name.split('/')
130+
131+
return {
132+
name: 'docs',
133+
params: {
134+
path: [firstSegment, ...remainingSegments, 'v', version],
135+
},
136+
}
137+
}
138+
139+
useCommandPaletteVersionCommands(commandPalettePackageContext, docsVersionRoute)
127140
128141
const pageTitle = computed(() => {
129142
if (!packageName.value) return t('package.docs.page_title')

app/pages/package-timeline/[[org]]/[packageName].vue

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ const packageName = computed(() =>
2323
const version = computed(() => route.params.version)
2424
2525
const { data: pkg } = usePackage(packageName, version)
26+
const { versions: commandPaletteVersions, ensureLoaded: ensureCommandPaletteVersionsLoaded } =
27+
useCommandPalettePackageVersions(packageName)
2628
2729
const latestVersion = computed(() => {
2830
if (!pkg.value) return null
@@ -31,11 +33,32 @@ const latestVersion = computed(() => {
3133
return pkg.value.versions[latestTag] ?? null
3234
})
3335
36+
const commandPalettePackageContext = computed(() => {
37+
const packageData = pkg.value
38+
if (!packageData) return null
39+
40+
return {
41+
packageName: packageData.name,
42+
resolvedVersion: version.value ?? packageData['dist-tags']?.latest ?? null,
43+
latestVersion: packageData['dist-tags']?.latest ?? null,
44+
versions: commandPaletteVersions.value ?? Object.keys(packageData.versions ?? {}),
45+
}
46+
})
47+
48+
useCommandPalettePackageContext(commandPalettePackageContext, {
49+
onOpen: ensureCommandPaletteVersionsLoaded,
50+
})
51+
useCommandPalettePackageCommands(commandPalettePackageContext)
52+
3453
const versionUrlPattern = computed(() => {
3554
const { org, packageName: name } = route.params
3655
return `/package-timeline/${org ? `${org}/` : ''}${name}/v/{version}`
3756
})
3857
58+
useCommandPaletteVersionCommands(commandPalettePackageContext, nextVersion =>
59+
packageTimelineRoute(packageName.value, nextVersion),
60+
)
61+
3962
function packageRoute(ver: string): RouteLocationRaw {
4063
return {
4164
name: 'package-version',

app/pages/package/[[org]]/[name].vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,9 @@ const versionUrlPattern = computed(
485485
() => `/package/${pkg.value?.name || packageName.value}/v/{version}`,
486486
)
487487
488-
useCommandPaletteVersionCommands(commandPalettePackageContext, versionUrlPattern)
488+
useCommandPaletteVersionCommands(commandPalettePackageContext, version =>
489+
packageRoute(packageName.value, version),
490+
)
489491
490492
const dependencyCount = computed(() => getDependencyCount(displayVersion.value))
491493

app/utils/router.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,16 @@ export function diffRoute(
5252
},
5353
}
5454
}
55+
56+
export function packageTimelineRoute(packageName: string, version: string): RouteLocationRaw {
57+
const { org, name } = splitPackageName(packageName)
58+
59+
return {
60+
name: 'timeline',
61+
params: {
62+
org: org || undefined,
63+
packageName: name,
64+
version: version.replace(/\s+/g, ''),
65+
},
66+
}
67+
}

test/nuxt/composables/use-command-palette-commands.spec.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { afterEach, describe, expect, it, vi } from 'vitest'
22
import { computed, defineComponent, h, ref, watchEffect, type Ref } from 'vue'
3+
import type { RouteLocationRaw } from 'vue-router'
34
import { mockNuxtImport, mountSuspended } from '@nuxt/test-utils/runtime'
45
import { downloadPackageTarball } from '~/utils/package-download'
56
import type {
@@ -49,7 +50,7 @@ async function captureCommandPalette(options?: {
4950
npmUser?: string | null
5051
atprotoHandle?: string | null
5152
packageContext?: CommandPalettePackageContext | null
52-
versionUrlPattern?: string
53+
versionRoute?: (version: string) => RouteLocationRaw
5354
contextCommands?: CommandPaletteContextCommandInput[]
5455
}) {
5556
const groupedCommands = ref<CommandPaletteCommandGroup[]>([]) as Ref<CommandPaletteCommandGroup[]>
@@ -76,10 +77,7 @@ async function captureCommandPalette(options?: {
7677
if (options?.packageContext) {
7778
setPackageContext(options.packageContext)
7879
useCommandPalettePackageCommands(() => options.packageContext ?? null)
79-
useCommandPaletteVersionCommands(
80-
() => options.packageContext ?? null,
81-
() => options.versionUrlPattern,
82-
)
80+
useCommandPaletteVersionCommands(() => options.packageContext ?? null, options.versionRoute)
8381
} else {
8482
clearPackageContext()
8583
}
@@ -232,6 +230,14 @@ describe('useCommandPaletteCommands', () => {
232230
expect(flatCommands.value.find(command => command.id === 'package-diff')).toBeTruthy()
233231
expect(flatCommands.value.find(command => command.id === 'package-download')).toBeTruthy()
234232
expect(flatCommands.value.find(command => command.id === 'package-main')?.to).toBeTruthy()
233+
expect(flatCommands.value.find(command => command.id === 'package-timeline')?.to).toEqual({
234+
name: 'timeline',
235+
params: {
236+
org: undefined,
237+
packageName: 'vue',
238+
version: '3.4.0',
239+
},
240+
})
235241
expect(groupedCommands.value.at(-1)?.id).toBe('versions')
236242
expect(groupedCommands.value.at(-1)?.items[0]?.id).toBe('version:3.4.0')
237243
expect(groupedCommands.value.at(-1)?.items[0]?.active).toBe(true)
@@ -345,7 +351,7 @@ describe('useCommandPaletteCommands', () => {
345351
wrapper.unmount()
346352
})
347353

348-
it('keeps version navigation on the current surface when a version URL pattern is provided', async () => {
354+
it('keeps version navigation on the current surface when a version route builder is provided', async () => {
349355
const { wrapper, flatCommands, routePath } = await captureCommandPalette({
350356
route: '/package-code/vue/v/3.4.2/src/index.ts',
351357
packageContext: {
@@ -354,7 +360,7 @@ describe('useCommandPaletteCommands', () => {
354360
latestVersion: '4.0.0',
355361
versions: ['4.0.0', '3.5.0', '3.4.2'],
356362
},
357-
versionUrlPattern: '/package-code/vue/v/{version}/src/index.ts',
363+
versionRoute: version => `/package-code/vue/v/${version}/src/index.ts`,
358364
})
359365

360366
const versionCommand = flatCommands.value.find(command => command.id === 'version:3.5.0')
@@ -513,6 +519,14 @@ describe('useCommandPaletteCommands', () => {
513519
path: ['@scope', 'pkg', 'v', '1.0.0'],
514520
},
515521
})
522+
expect(flatCommands.value.find(command => command.id === 'package-timeline')?.to).toEqual({
523+
name: 'timeline',
524+
params: {
525+
org: '@scope',
526+
packageName: 'pkg',
527+
version: '1.0.0',
528+
},
529+
})
516530

517531
wrapper.unmount()
518532
})

0 commit comments

Comments
 (0)