Skip to content

Commit a444140

Browse files
refactor(ui): move package external links to component (#2077)
1 parent a170292 commit a444140

File tree

3 files changed

+165
-105
lines changed

3 files changed

+165
-105
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<script setup lang="ts">
2+
import type { IconClass } from '~/types'
3+
4+
const props = defineProps<{
5+
pkg: SlimPackument
6+
jsrInfo?: JsrPackageInfo
7+
}>()
8+
9+
const displayVersion = computed(() => props.pkg?.requestedVersion ?? null)
10+
const { repositoryUrl } = useRepositoryUrl(displayVersion)
11+
const { meta: repoMeta, repoRef, stars, starsLink, forks, forksLink } = useRepoMeta(repositoryUrl)
12+
const compactNumberFormatter = useCompactNumberFormatter()
13+
14+
const homepageUrl = computed(() => {
15+
const homepage = displayVersion.value?.homepage
16+
if (!homepage) return null
17+
18+
// Don't show homepage if it's the same as the repository URL
19+
if (repositoryUrl.value && areUrlsEquivalent(homepage, repositoryUrl.value)) {
20+
return null
21+
}
22+
23+
return homepage
24+
})
25+
26+
const fundingUrl = computed(() => {
27+
let funding = displayVersion.value?.funding
28+
if (Array.isArray(funding)) funding = funding[0]
29+
30+
if (!funding) return null
31+
32+
return typeof funding === 'string' ? funding : funding.url
33+
})
34+
35+
const PROVIDER_ICONS: Record<string, IconClass> = {
36+
github: 'i-simple-icons:github',
37+
gitlab: 'i-simple-icons:gitlab',
38+
bitbucket: 'i-simple-icons:bitbucket',
39+
codeberg: 'i-simple-icons:codeberg',
40+
gitea: 'i-simple-icons:gitea',
41+
forgejo: 'i-simple-icons:forgejo',
42+
gitee: 'i-simple-icons:gitee',
43+
sourcehut: 'i-simple-icons:sourcehut',
44+
tangled: 'i-custom:tangled',
45+
radicle: 'i-lucide:network', // Radicle is a P2P network, using network icon
46+
}
47+
48+
const repoProviderIcon = computed((): IconClass => {
49+
const provider = repoRef.value?.provider
50+
if (!provider) return 'i-simple-icons:github'
51+
return PROVIDER_ICONS[provider] ?? 'i-lucide:code'
52+
})
53+
</script>
54+
55+
<template>
56+
<ul class="flex flex-wrap items-center gap-x-3 gap-y-1.5 sm:gap-4 list-none m-0 p-0 mt-3 text-sm">
57+
<li v-if="repositoryUrl">
58+
<LinkBase :to="repositoryUrl" :classicon="repoProviderIcon">
59+
<span v-if="repoRef">
60+
{{ repoRef.owner }}<span class="opacity-50">/</span>{{ repoRef.repo }}
61+
</span>
62+
<span v-else>{{ $t('package.links.repo') }}</span>
63+
</LinkBase>
64+
</li>
65+
<li v-if="repositoryUrl && repoMeta && starsLink">
66+
<LinkBase :to="starsLink" classicon="i-lucide:star">
67+
{{ compactNumberFormatter.format(stars) }}
68+
</LinkBase>
69+
</li>
70+
<li v-if="forks && forksLink">
71+
<LinkBase :to="forksLink" classicon="i-lucide:git-fork">
72+
{{ compactNumberFormatter.format(forks) }}
73+
</LinkBase>
74+
</li>
75+
<li class="basis-full sm:hidden" />
76+
<li v-if="homepageUrl">
77+
<LinkBase :to="homepageUrl" classicon="i-lucide:link">
78+
{{ $t('package.links.homepage') }}
79+
</LinkBase>
80+
</li>
81+
<li v-if="displayVersion?.bugs?.url">
82+
<LinkBase :to="displayVersion.bugs.url" classicon="i-lucide:circle-alert">
83+
{{ $t('package.links.issues') }}
84+
</LinkBase>
85+
</li>
86+
<li>
87+
<LinkBase
88+
:to="`https://www.npmjs.com/package/${pkg.name}`"
89+
:title="$t('common.view_on.npm')"
90+
classicon="i-simple-icons:npm"
91+
>
92+
npm
93+
</LinkBase>
94+
</li>
95+
<li v-if="jsrInfo?.exists && jsrInfo.url">
96+
<LinkBase :to="jsrInfo.url" :title="$t('badges.jsr.title')" classicon="i-simple-icons:jsr">
97+
{{ $t('package.links.jsr') }}
98+
</LinkBase>
99+
</li>
100+
<li v-if="fundingUrl">
101+
<LinkBase :to="fundingUrl" classicon="i-lucide:heart">
102+
{{ $t('package.links.fund') }}
103+
</LinkBase>
104+
</li>
105+
</ul>
106+
</template>

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

Lines changed: 2 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
<script setup lang="ts">
2-
import type { JsrPackageInfo } from '#shared/types/jsr'
3-
import type { IconClass } from '~/types'
42
import { assertValidPackageName } from '#shared/utils/npm'
5-
import { areUrlsEquivalent } from '#shared/utils/url'
63
import { getDependencyCount } from '~/utils/npm/dependency-count'
7-
import { detectPublishSecurityDowngradeForVersion } from '~/utils/publish-security'
8-
import { useInstallSizeDiff } from '~/composables/useInstallSizeDiff'
9-
import { useViewOnGitProvider } from '~/composables/useViewOnGitProvider'
104
115
defineOgImageComponent('Package', {
126
name: () => packageName.value,
@@ -390,50 +384,10 @@ const totalDepsCount = computed(() => {
390384
391385
const { repositoryUrl } = useRepositoryUrl(displayVersion)
392386
393-
const { meta: repoMeta, repoRef, stars, starsLink, forks, forksLink } = useRepoMeta(repositoryUrl)
394-
395-
const PROVIDER_ICONS: Record<string, IconClass> = {
396-
github: 'i-simple-icons:github',
397-
gitlab: 'i-simple-icons:gitlab',
398-
bitbucket: 'i-simple-icons:bitbucket',
399-
codeberg: 'i-simple-icons:codeberg',
400-
gitea: 'i-simple-icons:gitea',
401-
forgejo: 'i-simple-icons:forgejo',
402-
gitee: 'i-simple-icons:gitee',
403-
sourcehut: 'i-simple-icons:sourcehut',
404-
tangled: 'i-custom:tangled',
405-
radicle: 'i-lucide:network', // Radicle is a P2P network, using network icon
406-
}
407-
408-
const repoProviderIcon = computed((): IconClass => {
409-
const provider = repoRef.value?.provider
410-
if (!provider) return 'i-simple-icons:github'
411-
return PROVIDER_ICONS[provider] ?? 'i-lucide:code'
412-
})
387+
const { repoRef } = useRepoMeta(repositoryUrl)
413388
414389
const viewOnGitProvider = useViewOnGitProvider(() => repoRef.value?.provider)
415390
416-
const homepageUrl = computed(() => {
417-
const homepage = displayVersion.value?.homepage
418-
if (!homepage) return null
419-
420-
// Don't show homepage if it's the same as the repository URL
421-
if (repositoryUrl.value && areUrlsEquivalent(homepage, repositoryUrl.value)) {
422-
return null
423-
}
424-
425-
return homepage
426-
})
427-
428-
const fundingUrl = computed(() => {
429-
let funding = displayVersion.value?.funding
430-
if (Array.isArray(funding)) funding = funding[0]
431-
432-
if (!funding) return null
433-
434-
return typeof funding === 'string' ? funding : funding.url
435-
})
436-
437391
// Check if a version has provenance/attestations
438392
// The dist object may have attestations that aren't in the base type
439393
function hasProvenance(version: PackumentVersion | null): boolean {
@@ -496,7 +450,6 @@ const versionUrlPattern = computed(
496450
const dependencyCount = computed(() => getDependencyCount(displayVersion.value))
497451
498452
const numberFormatter = useNumberFormatter()
499-
const compactNumberFormatter = useCompactNumberFormatter()
500453
const bytesFormatter = useBytesFormatter()
501454
502455
useHead({
@@ -578,63 +531,7 @@ const showSkeleton = shallowRef(false)
578531
</p>
579532
</div>
580533

581-
<!-- External links -->
582-
<ul
583-
class="flex flex-wrap items-center gap-x-3 gap-y-1.5 sm:gap-4 list-none m-0 p-0 mt-3 text-sm"
584-
>
585-
<li v-if="repositoryUrl">
586-
<LinkBase :to="repositoryUrl" :classicon="repoProviderIcon">
587-
<span v-if="repoRef">
588-
{{ repoRef.owner }}<span class="opacity-50">/</span>{{ repoRef.repo }}
589-
</span>
590-
<span v-else>{{ $t('package.links.repo') }}</span>
591-
</LinkBase>
592-
</li>
593-
<li v-if="repositoryUrl && repoMeta && starsLink">
594-
<LinkBase :to="starsLink" classicon="i-lucide:star">
595-
{{ compactNumberFormatter.format(stars) }}
596-
</LinkBase>
597-
</li>
598-
<li v-if="forks && forksLink">
599-
<LinkBase :to="forksLink" classicon="i-lucide:git-fork">
600-
{{ compactNumberFormatter.format(forks) }}
601-
</LinkBase>
602-
</li>
603-
<li class="basis-full sm:hidden" />
604-
<li v-if="homepageUrl">
605-
<LinkBase :to="homepageUrl" classicon="i-lucide:link">
606-
{{ $t('package.links.homepage') }}
607-
</LinkBase>
608-
</li>
609-
<li v-if="displayVersion?.bugs?.url">
610-
<LinkBase :to="displayVersion.bugs.url" classicon="i-lucide:circle-alert">
611-
{{ $t('package.links.issues') }}
612-
</LinkBase>
613-
</li>
614-
<li>
615-
<LinkBase
616-
:to="`https://www.npmjs.com/package/${pkg.name}`"
617-
:title="$t('common.view_on.npm')"
618-
classicon="i-simple-icons:npm"
619-
>
620-
npm
621-
</LinkBase>
622-
</li>
623-
<li v-if="jsrInfo?.exists && jsrInfo.url">
624-
<LinkBase
625-
:to="jsrInfo.url"
626-
:title="$t('badges.jsr.title')"
627-
classicon="i-simple-icons:jsr"
628-
>
629-
{{ $t('package.links.jsr') }}
630-
</LinkBase>
631-
</li>
632-
<li v-if="fundingUrl">
633-
<LinkBase :to="fundingUrl" classicon="i-lucide:heart">
634-
{{ $t('package.links.fund') }}
635-
</LinkBase>
636-
</li>
637-
</ul>
534+
<PackageExternalLinks :pkg :jsrInfo />
638535
<PackageMetricsBadges
639536
v-if="resolvedVersion"
640537
:package-name="packageName"

test/nuxt/a11y.spec.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ import {
219219
DiffSkipBlock,
220220
DiffTable,
221221
DiffViewerPanel,
222+
PackageExternalLinks,
222223
} from '#components'
223224

224225
// Server variant components must be imported directly to test the server-side render
@@ -672,6 +673,62 @@ describe('component accessibility audits', () => {
672673
})
673674
})
674675

676+
describe('PackageExternalLinks', () => {
677+
it('should have no accessibility violations', async () => {
678+
const component = await mountSuspended(PackageExternalLinks, {
679+
props: {
680+
pkg: {
681+
'_id': 'react',
682+
'name': 'react',
683+
'dist-tags': { latest: '18.2.0' },
684+
'time': {
685+
'created': '2013-01-31T01:07:45.050Z',
686+
'modified': '2024-03-14T00:00:00.000Z',
687+
'18.2.0': '2024-03-14T00:00:00.000Z',
688+
},
689+
'requestedVersion': {
690+
version: '18.2.0',
691+
_npmVersion: '18.2.0',
692+
homepage: 'https://react.dev',
693+
repository: {
694+
type: 'git',
695+
url: 'https://github.com/facebook/react.git',
696+
},
697+
bugs: {
698+
url: 'https://github.com/facebook/react/issues',
699+
},
700+
funding: 'https://github.com/sponsors/facebook',
701+
dist: {
702+
shasum: 'abc123def456',
703+
tarball: 'https://registry.npmjs.org/react/-/react-18.2.0.tgz',
704+
signatures: [],
705+
},
706+
deprecated: undefined,
707+
keywords: [],
708+
license: 'MIT',
709+
name: 'react',
710+
time: '2024-03-14T00:00:00.000Z',
711+
_id: 'react@18.2.0',
712+
},
713+
'versions': {
714+
'18.2.0': {
715+
version: '18.2.0',
716+
hasProvenance: false,
717+
tags: [],
718+
},
719+
},
720+
},
721+
jsrInfo: {
722+
exists: true,
723+
url: 'https://jsr.io/@react/react',
724+
},
725+
},
726+
})
727+
const results = await runAxe(component)
728+
expect(results.violations).toEqual([])
729+
})
730+
})
731+
675732
describe('PackageCard', () => {
676733
const mockResult = {
677734
package: {

0 commit comments

Comments
 (0)