[READ-ONLY] a fast, modern browser for the npm registry
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: add proper route segments for package code (#1301)

authored by

Marcus Blättermann and committed by
GitHub
c3090e02 9aae0786

+120 -78
+8 -7
app/components/Code/DirectoryListing.vue
··· 1 1 <script setup lang="ts"> 2 2 import type { PackageFileTree } from '#shared/types' 3 3 import type { RouteLocationRaw } from 'vue-router' 4 + import type { RouteNamedMap } from 'vue-router/auto-routes' 4 5 import { ADDITIONAL_ICONS, getFileIcon } from '~/utils/file-icons' 5 6 6 7 const props = defineProps<{ 7 8 tree: PackageFileTree[] 8 9 currentPath: string 9 10 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'> 12 12 }>() 13 13 14 14 // Get the current directory's contents ··· 41 41 42 42 // Build route object for a path 43 43 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('/')] 48 44 return { 49 45 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 + }, 51 52 } 52 53 } 53 54
+9 -5
app/components/Code/FileTree.vue
··· 1 1 <script setup lang="ts"> 2 2 import type { PackageFileTree } from '#shared/types' 3 3 import type { RouteLocationRaw } from 'vue-router' 4 + import type { RouteNamedMap } from 'vue-router/auto-routes' 4 5 import { ADDITIONAL_ICONS, getFileIcon } from '~/utils/file-icons' 5 6 6 7 const props = defineProps<{ 7 8 tree: PackageFileTree[] 8 9 currentPath: string 9 10 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'> 12 12 depth?: number 13 13 }>() 14 14 ··· 23 23 24 24 // Build route object for a file path 25 25 function getFileRoute(nodePath: string): RouteLocationRaw { 26 - const pathSegments = [...props.basePath, ...nodePath.split('/')] 27 26 return { 28 27 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 + }, 30 34 } 31 35 } 32 36 ··· 75 79 :tree="node.children" 76 80 :current-path="currentPath" 77 81 :base-url="baseUrl" 78 - :base-path="basePath" 82 + :base-route="baseRoute" 79 83 :depth="depth + 1" 80 84 /> 81 85 </template>
+3 -3
app/components/Code/MobileTreeDrawer.vue
··· 1 1 <script setup lang="ts"> 2 2 import type { PackageFileTree } from '#shared/types' 3 + import type { RouteNamedMap } from 'vue-router/auto-routes' 3 4 4 5 defineProps<{ 5 6 tree: PackageFileTree[] 6 7 currentPath: string 7 8 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'> 10 10 }>() 11 11 12 12 const isOpen = shallowRef(false) ··· 75 75 :tree="tree" 76 76 :current-path="currentPath" 77 77 :base-url="baseUrl" 78 - :base-path="basePath" 78 + :base-route="baseRoute" 79 79 /> 80 80 </aside> 81 81 </Transition>
+17 -3
app/components/Package/InstallScripts.vue
··· 1 1 <script setup lang="ts"> 2 2 import { getOutdatedTooltip, getVersionClass } from '~/utils/npm/outdated-dependencies' 3 + import type { RouteLocationRaw } from 'vue-router' 3 4 4 5 const props = defineProps<{ 5 6 packageName: string ··· 11 12 } 12 13 }>() 13 14 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 + } 16 27 } 17 28 18 29 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 + > = {} 20 34 for (const scriptName of props.installScripts.scripts) { 21 35 const content = props.installScripts.content?.[scriptName] 22 36 if (!content) continue
+46 -47
app/pages/package-code/[...path].vue app/pages/package-code/[[org]]/[packageName]/v/[version]/[...filePath].vue
··· 7 7 8 8 definePageMeta({ 9 9 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 + ], 12 16 }) 13 17 14 18 const route = useRoute('code') ··· 19 23 // /code/nuxt/v/4.2.0/src/index.ts → packageName: "nuxt", version: "4.2.0", filePath: "src/index.ts" 20 24 // /code/@nuxt/kit/v/1.0.0 → packageName: "@nuxt/kit", version: "1.0.0", filePath: null 21 25 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 39 31 40 32 return { packageName, version, filePath } 41 33 }) ··· 45 37 const filePathOrig = computed(() => parsedRoute.value.filePath) 46 38 const filePath = computed(() => parsedRoute.value.filePath?.replace(/\/$/, '')) 47 39 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 + 48 53 // Fetch package data for version list 49 54 const { data: pkg } = usePackage(packageName) 50 55 51 56 // 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 + ) 56 65 57 66 // Fetch file tree 58 67 const { data: fileTree, status: treeStatus } = useFetch<PackageFileTreeResponse>( ··· 192 201 }) 193 202 194 203 // 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 + }) 198 209 } 199 - 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 210 206 211 // Extract org name from scoped package 207 212 const orgName = computed(() => { ··· 244 249 } 245 250 246 251 // 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)}`) 254 253 255 254 // Toggle markdown view mode 256 255 const markdownViewModes = [ ··· 350 349 > 351 350 <NuxtLink 352 351 v-if="filePath" 353 - :to="getCodeUrl()" 352 + :to="getCurrentCodeUrlWithPath()" 354 353 class="text-fg-muted hover:text-fg transition-colors shrink-0" 355 354 > 356 355 {{ $t('code.root') }} ··· 360 359 <span class="text-fg-subtle">/</span> 361 360 <NuxtLink 362 361 v-if="i < breadcrumbs.length - 1" 363 - :to="getCodeUrl(crumb.path)" 362 + :to="getCurrentCodeUrlWithPath(crumb.path)" 364 363 class="text-fg-muted hover:text-fg transition-colors" 365 364 > 366 365 {{ crumb.name }} ··· 402 401 <CodeFileTree 403 402 :tree="fileTree.tree" 404 403 :current-path="filePath ?? ''" 405 - :base-url="getCodeUrl()" 406 - :base-path="basePath" 404 + :base-url="getCurrentCodeUrlWithPath()" 405 + :base-route="route" 407 406 /> 408 407 </aside> 409 408 ··· 558 557 <CodeDirectoryListing 559 558 :tree="fileTree.tree" 560 559 :current-path="filePath ?? ''" 561 - :base-url="getCodeUrl()" 562 - :base-path="basePath" 560 + :base-url="getCurrentCodeUrlWithPath()" 561 + :base-route="route" 563 562 /> 564 563 </template> 565 564 </div> ··· 572 571 v-if="fileTree" 573 572 :tree="fileTree.tree" 574 573 :current-path="filePath ?? ''" 575 - :base-url="getCodeUrl()" 576 - :base-path="basePath" 574 + :base-url="getCurrentCodeUrlWithPath()" 575 + :base-route="route" 577 576 /> 578 577 </Teleport> 579 578 </ClientOnly>
+22 -8
app/pages/package/[[org]]/[name].vue
··· 17 17 import { useModal } from '~/composables/useModal' 18 18 import { useAtproto } from '~/composables/atproto/useAtproto' 19 19 import { togglePackageLike } from '~/utils/atproto/likes' 20 + import type { RouteLocationRaw } from 'vue-router' 20 21 21 22 defineOgImageComponent('Package', { 22 23 name: () => packageName.value, ··· 557 558 twitterDescription: () => pkg.value?.description ?? '', 558 559 }) 559 560 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 + 560 577 onKeyStroke( 561 578 e => isKeyWithoutModifiers(e, '.') && !isEditableElement(e.target), 562 579 e => { 563 - if (pkg.value == null || resolvedVersion.value == null) return 580 + if (codeLink.value === null) return 564 581 e.preventDefault() 565 - navigateTo({ 566 - name: 'code', 567 - params: { 568 - path: [pkg.value.name, 'v', resolvedVersion.value], 569 - }, 570 - }) 582 + 583 + navigateTo(codeLink.value) 571 584 }, 572 585 { dedupe: true }, 573 586 ) ··· 719 732 {{ $t('package.links.docs') }} 720 733 </LinkBase> 721 734 <LinkBase 735 + v-if="codeLink" 722 736 variant="button-secondary" 723 - :to="{ name: 'code', params: { path: [pkg.name, 'v', resolvedVersion] } }" 737 + :to="codeLink" 724 738 aria-keyshortcuts="." 725 739 classicon="i-carbon:code" 726 740 >
+15 -5
test/nuxt/a11y.spec.ts
··· 921 921 tree: mockTree, 922 922 currentPath: '', 923 923 baseUrl: '/package-code/vue', 924 - basePath: ['vue', 'v', '3.0.0'], 924 + baseRoute: { 925 + params: { packageName: 'vue', version: '3.0.0', filePath: '' }, 926 + }, 925 927 }, 926 928 }) 927 929 const results = await runAxe(component) ··· 934 936 tree: mockTree, 935 937 currentPath: 'src', 936 938 baseUrl: '/package-code/vue', 937 - basePath: ['vue', 'v', '3.0.0'], 939 + baseRoute: { 940 + params: { packageName: 'vue', version: '3.0.0', filePath: '' }, 941 + }, 938 942 }, 939 943 }) 940 944 const results = await runAxe(component) ··· 959 963 tree: mockTree, 960 964 currentPath: '', 961 965 baseUrl: '/package-code/vue', 962 - basePath: ['vue', 'v', '3.0.0'], 966 + baseRoute: { 967 + params: { packageName: 'vue', version: '3.0.0', filePath: '' }, 968 + }, 963 969 }, 964 970 }) 965 971 const results = await runAxe(component) ··· 972 978 tree: mockTree, 973 979 currentPath: 'src/index.ts', 974 980 baseUrl: '/package-code/vue', 975 - basePath: ['vue', 'v', '3.0.0'], 981 + baseRoute: { 982 + params: { packageName: 'vue', version: '3.0.0', filePath: '' }, 983 + }, 976 984 }, 977 985 }) 978 986 const results = await runAxe(component) ··· 1222 1230 tree: mockTree, 1223 1231 currentPath: '', 1224 1232 baseUrl: '/package-code/vue', 1225 - basePath: ['vue', 'v', '3.0.0'], 1233 + baseRoute: { 1234 + params: { packageName: 'vue', version: '3.0.0', filePath: '' }, 1235 + }, 1226 1236 }, 1227 1237 }) 1228 1238 const results = await runAxe(component)