Skip to content

Commit c3090e0

Browse files
refactor: add proper route segments for package code (#1301)
1 parent 9aae078 commit c3090e0

File tree

7 files changed

+120
-78
lines changed

7 files changed

+120
-78
lines changed

app/components/Code/DirectoryListing.vue

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
<script setup lang="ts">
22
import type { PackageFileTree } from '#shared/types'
33
import type { RouteLocationRaw } from 'vue-router'
4+
import type { RouteNamedMap } from 'vue-router/auto-routes'
45
import { ADDITIONAL_ICONS, getFileIcon } from '~/utils/file-icons'
56
67
const props = defineProps<{
78
tree: PackageFileTree[]
89
currentPath: string
910
baseUrl: string
10-
/** Base path segments for the code route (e.g., ['nuxt', 'v', '4.2.0']) */
11-
basePath: string[]
11+
baseRoute: Pick<RouteNamedMap['code'], 'params'>
1212
}>()
1313
1414
// Get the current directory's contents
@@ -41,13 +41,14 @@ const parentPath = computed(() => {
4141
4242
// Build route object for a path
4343
function getCodeRoute(nodePath?: string): RouteLocationRaw {
44-
if (!nodePath) {
45-
return { name: 'code', params: { path: props.basePath as [string, ...string[]] } }
46-
}
47-
const pathSegments = [...props.basePath, ...nodePath.split('/')]
4844
return {
4945
name: 'code',
50-
params: { path: pathSegments as [string, ...string[]] },
46+
params: {
47+
org: props.baseRoute.params.org,
48+
packageName: props.baseRoute.params.packageName,
49+
version: props.baseRoute.params.version,
50+
filePath: nodePath ?? '',
51+
},
5152
}
5253
}
5354

app/components/Code/FileTree.vue

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
<script setup lang="ts">
22
import type { PackageFileTree } from '#shared/types'
33
import type { RouteLocationRaw } from 'vue-router'
4+
import type { RouteNamedMap } from 'vue-router/auto-routes'
45
import { ADDITIONAL_ICONS, getFileIcon } from '~/utils/file-icons'
56
67
const props = defineProps<{
78
tree: PackageFileTree[]
89
currentPath: string
910
baseUrl: string
10-
/** Base path segments for the code route (e.g., ['nuxt', 'v', '4.2.0']) */
11-
basePath: string[]
11+
baseRoute: Pick<RouteNamedMap['code'], 'params'>
1212
depth?: number
1313
}>()
1414
@@ -23,10 +23,14 @@ function isNodeActive(node: PackageFileTree): boolean {
2323
2424
// Build route object for a file path
2525
function getFileRoute(nodePath: string): RouteLocationRaw {
26-
const pathSegments = [...props.basePath, ...nodePath.split('/')]
2726
return {
2827
name: 'code',
29-
params: { path: pathSegments as [string, ...string[]] },
28+
params: {
29+
org: props.baseRoute.params.org,
30+
packageName: props.baseRoute.params.packageName,
31+
version: props.baseRoute.params.version,
32+
filePath: nodePath ?? '',
33+
},
3034
}
3135
}
3236
@@ -75,7 +79,7 @@ watch(
7579
:tree="node.children"
7680
:current-path="currentPath"
7781
:base-url="baseUrl"
78-
:base-path="basePath"
82+
:base-route="baseRoute"
7983
:depth="depth + 1"
8084
/>
8185
</template>

app/components/Code/MobileTreeDrawer.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
<script setup lang="ts">
22
import type { PackageFileTree } from '#shared/types'
3+
import type { RouteNamedMap } from 'vue-router/auto-routes'
34
45
defineProps<{
56
tree: PackageFileTree[]
67
currentPath: string
78
baseUrl: string
8-
/** Base path segments for the code route (e.g., ['nuxt', 'v', '4.2.0']) */
9-
basePath: string[]
9+
baseRoute: Pick<RouteNamedMap['code'], 'params'>
1010
}>()
1111
1212
const isOpen = shallowRef(false)
@@ -75,7 +75,7 @@ watch(isOpen, open => (isLocked.value = open))
7575
:tree="tree"
7676
:current-path="currentPath"
7777
:base-url="baseUrl"
78-
:base-path="basePath"
78+
:base-route="baseRoute"
7979
/>
8080
</aside>
8181
</Transition>

app/components/Package/InstallScripts.vue

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
22
import { getOutdatedTooltip, getVersionClass } from '~/utils/npm/outdated-dependencies'
3+
import type { RouteLocationRaw } from 'vue-router'
34
45
const props = defineProps<{
56
packageName: string
@@ -11,12 +12,25 @@ const props = defineProps<{
1112
}
1213
}>()
1314
14-
function getCodeLink(filePath: string): string {
15-
return `/code/${props.packageName}/v/${props.version}/${filePath}`
15+
function getCodeLink(filePath: string): RouteLocationRaw {
16+
const split = props.packageName.split('/')
17+
18+
return {
19+
name: 'code',
20+
params: {
21+
org: split.length === 2 ? split[0] : null,
22+
packageName: split.length === 2 ? split[1]! : split[0]!,
23+
version: props.version,
24+
filePath: filePath,
25+
},
26+
}
1627
}
1728
1829
const scriptParts = computed(() => {
19-
const parts: Record<string, { prefix: string | null; filePath: string | null; link: string }> = {}
30+
const parts: Record<
31+
string,
32+
{ prefix: string | null; filePath: string | null; link: RouteLocationRaw }
33+
> = {}
2034
for (const scriptName of props.installScripts.scripts) {
2135
const content = props.installScripts.content?.[scriptName]
2236
if (!content) continue

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

Lines changed: 46 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ import type {
77
88
definePageMeta({
99
name: 'code',
10-
path: '/package-code/:path+',
11-
alias: ['/package/code/:path+', '/code/:path+'],
10+
path: '/package-code/:org?/:packageName/v/:version/:filePath(.*)?',
11+
alias: [
12+
'/package/code/:org?/:packageName/v/:version/:filePath(.*)?',
13+
'/package/code/:packageName/v/:version/:filePath(.*)?',
14+
// '/code/@:org?/:packageName/v/:version/:filePath(.*)?',
15+
],
1216
})
1317
1418
const route = useRoute('code')
@@ -19,23 +23,11 @@ const route = useRoute('code')
1923
// /code/nuxt/v/4.2.0/src/index.ts → packageName: "nuxt", version: "4.2.0", filePath: "src/index.ts"
2024
// /code/@nuxt/kit/v/1.0.0 → packageName: "@nuxt/kit", version: "1.0.0", filePath: null
2125
const parsedRoute = computed(() => {
22-
const segments = route.params.path
23-
24-
// Find the /v/ separator for version
25-
const vIndex = segments.indexOf('v')
26-
if (vIndex === -1 || vIndex >= segments.length - 1) {
27-
// No version specified - redirect or error
28-
return {
29-
packageName: segments.join('/'),
30-
version: null as string | null,
31-
filePath: null as string | null,
32-
}
33-
}
34-
35-
const packageName = segments.slice(0, vIndex).join('/')
36-
const afterVersion = segments.slice(vIndex + 1)
37-
const version = afterVersion[0] ?? null
38-
const filePath = afterVersion.length > 1 ? afterVersion.slice(1).join('/') : null
26+
const packageName = route.params.org
27+
? `${route.params.org}/${route.params.packageName}`
28+
: route.params.packageName
29+
const version = route.params.version
30+
const filePath = route.params.filePath || null
3931
4032
return { packageName, version, filePath }
4133
})
@@ -45,14 +37,31 @@ const version = computed(() => parsedRoute.value.version)
4537
const filePathOrig = computed(() => parsedRoute.value.filePath)
4638
const filePath = computed(() => parsedRoute.value.filePath?.replace(/\/$/, ''))
4739
40+
// Navigation helper - build URL for a path
41+
function getCodeUrl(args: {
42+
org?: string
43+
packageName: string
44+
version: string
45+
filePath?: string
46+
}): string {
47+
const base = args.org
48+
? `/package-code/${args.org}/${args.packageName}/v/${args.version}`
49+
: `/package-code/${args.packageName}/v/${args.version}`
50+
return args.filePath ? `${base}/${args.filePath}` : base
51+
}
52+
4853
// Fetch package data for version list
4954
const { data: pkg } = usePackage(packageName)
5055
5156
// URL pattern for version selector - includes file path if present
52-
const versionUrlPattern = computed(() => {
53-
const base = `/package-code/${packageName.value}/v/{version}`
54-
return filePath.value ? `${base}/${filePath.value}` : base
55-
})
57+
const versionUrlPattern = computed(() =>
58+
getCodeUrl({
59+
org: route.params.org,
60+
packageName: route.params.packageName,
61+
version: '{version}',
62+
filePath: filePath.value,
63+
}),
64+
)
5665
5766
// Fetch file tree
5867
const { data: fileTree, status: treeStatus } = useFetch<PackageFileTreeResponse>(
@@ -192,17 +201,13 @@ const breadcrumbs = computed(() => {
192201
})
193202
194203
// Navigation helper - build URL for a path
195-
function getCodeUrl(path?: string): string {
196-
const base = `/package-code/${packageName.value}/v/${version.value}`
197-
return path ? `${base}/${path}` : base
204+
function getCurrentCodeUrlWithPath(path?: string): string {
205+
return getCodeUrl({
206+
...route.params,
207+
filePath: path,
208+
})
198209
}
199210
200-
// Base path segments for route objects (e.g., ['nuxt', 'v', '4.2.0'] or ['@nuxt', 'kit', 'v', '1.0.0'])
201-
const basePath = computed(() => {
202-
const segments = packageName.value.split('/')
203-
return [...segments, 'v', version.value ?? '']
204-
})
205-
206211
// Extract org name from scoped package
207212
const orgName = computed(() => {
208213
const name = packageName.value
@@ -244,13 +249,7 @@ function copyPermalinkUrl() {
244249
}
245250
246251
// Canonical URL for this code page
247-
const canonicalUrl = computed(() => {
248-
let url = `https://npmx.dev/package-code/${packageName.value}/v/${version.value}`
249-
if (filePath.value) {
250-
url += `/${filePath.value}`
251-
}
252-
return url
253-
})
252+
const canonicalUrl = computed(() => `https://npmx.dev${getCodeUrl(route.params)}`)
254253
255254
// Toggle markdown view mode
256255
const markdownViewModes = [
@@ -350,7 +349,7 @@ defineOgImageComponent('Default', {
350349
>
351350
<NuxtLink
352351
v-if="filePath"
353-
:to="getCodeUrl()"
352+
:to="getCurrentCodeUrlWithPath()"
354353
class="text-fg-muted hover:text-fg transition-colors shrink-0"
355354
>
356355
{{ $t('code.root') }}
@@ -360,7 +359,7 @@ defineOgImageComponent('Default', {
360359
<span class="text-fg-subtle">/</span>
361360
<NuxtLink
362361
v-if="i < breadcrumbs.length - 1"
363-
:to="getCodeUrl(crumb.path)"
362+
:to="getCurrentCodeUrlWithPath(crumb.path)"
364363
class="text-fg-muted hover:text-fg transition-colors"
365364
>
366365
{{ crumb.name }}
@@ -402,8 +401,8 @@ defineOgImageComponent('Default', {
402401
<CodeFileTree
403402
:tree="fileTree.tree"
404403
:current-path="filePath ?? ''"
405-
:base-url="getCodeUrl()"
406-
:base-path="basePath"
404+
:base-url="getCurrentCodeUrlWithPath()"
405+
:base-route="route"
407406
/>
408407
</aside>
409408

@@ -558,8 +557,8 @@ defineOgImageComponent('Default', {
558557
<CodeDirectoryListing
559558
:tree="fileTree.tree"
560559
:current-path="filePath ?? ''"
561-
:base-url="getCodeUrl()"
562-
:base-path="basePath"
560+
:base-url="getCurrentCodeUrlWithPath()"
561+
:base-route="route"
563562
/>
564563
</template>
565564
</div>
@@ -572,8 +571,8 @@ defineOgImageComponent('Default', {
572571
v-if="fileTree"
573572
:tree="fileTree.tree"
574573
:current-path="filePath ?? ''"
575-
:base-url="getCodeUrl()"
576-
:base-path="basePath"
574+
:base-url="getCurrentCodeUrlWithPath()"
575+
:base-route="route"
577576
/>
578577
</Teleport>
579578
</ClientOnly>

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

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { detectPublishSecurityDowngradeForVersion } from '~/utils/publish-securi
1717
import { useModal } from '~/composables/useModal'
1818
import { useAtproto } from '~/composables/atproto/useAtproto'
1919
import { togglePackageLike } from '~/utils/atproto/likes'
20+
import type { RouteLocationRaw } from 'vue-router'
2021
2122
defineOgImageComponent('Package', {
2223
name: () => packageName.value,
@@ -557,17 +558,29 @@ useSeoMeta({
557558
twitterDescription: () => pkg.value?.description ?? '',
558559
})
559560
561+
const codeLink = computed((): RouteLocationRaw | null => {
562+
if (pkg.value == null || resolvedVersion.value == null) {
563+
return null
564+
}
565+
const split = pkg.value.name.split('/')
566+
return {
567+
name: 'code',
568+
params: {
569+
org: split.length === 2 ? split[0] : undefined,
570+
packageName: split.length === 2 ? split[1]! : split[0]!,
571+
version: resolvedVersion.value,
572+
filePath: '',
573+
},
574+
}
575+
})
576+
560577
onKeyStroke(
561578
e => isKeyWithoutModifiers(e, '.') && !isEditableElement(e.target),
562579
e => {
563-
if (pkg.value == null || resolvedVersion.value == null) return
580+
if (codeLink.value === null) return
564581
e.preventDefault()
565-
navigateTo({
566-
name: 'code',
567-
params: {
568-
path: [pkg.value.name, 'v', resolvedVersion.value],
569-
},
570-
})
582+
583+
navigateTo(codeLink.value)
571584
},
572585
{ dedupe: true },
573586
)
@@ -719,8 +732,9 @@ const showSkeleton = shallowRef(false)
719732
{{ $t('package.links.docs') }}
720733
</LinkBase>
721734
<LinkBase
735+
v-if="codeLink"
722736
variant="button-secondary"
723-
:to="{ name: 'code', params: { path: [pkg.name, 'v', resolvedVersion] } }"
737+
:to="codeLink"
724738
aria-keyshortcuts="."
725739
classicon="i-carbon:code"
726740
>

0 commit comments

Comments
 (0)