Skip to content

Commit eee891c

Browse files
committed
Merge branch 'main' of https://github.com/YFAnt/npmx.dev
2 parents d9614c4 + 9520fbf commit eee891c

4 files changed

Lines changed: 163 additions & 5 deletions

File tree

app/components/AppHeader.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,15 @@ withDefaults(
3030
<NuxtLink
3131
to="/search"
3232
class="link-subtle font-mono text-sm inline-flex items-center gap-2"
33+
aria-keyshortcuts="/"
3334
>
3435
search
3536
<kbd
3637
class="hidden sm:inline-flex items-center justify-center w-5 h-5 text-xs bg-bg-muted border border-border rounded"
37-
>/</kbd
38+
aria-hidden="true"
3839
>
40+
/
41+
</kbd>
3942
</NuxtLink>
4043
</li>
4144
<li v-if="showConnector" class="flex">

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

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
<script setup lang="ts">
2-
import { joinURL } from 'ufo'
3-
import type { PackumentVersion, NpmVersionDist, ReadmeResponse } from '#shared/types'
2+
import type { NpmVersionDist, PackumentVersion, ReadmeResponse } from '#shared/types'
43
import type { JsrPackageInfo } from '#shared/types/jsr'
54
import { assertValidPackageName } from '#shared/utils/npm'
5+
import { onKeyStroke } from '@vueuse/core'
6+
import { joinURL } from 'ufo'
7+
import { areUrlsEquivalent } from '#shared/utils/url'
68
79
definePageMeta({
810
name: 'package',
@@ -11,6 +13,8 @@ definePageMeta({
1113
1214
const route = useRoute('package')
1315
16+
const router = useRouter()
17+
1418
// Parse package name and optional version from URL
1519
// Patterns:
1620
// /nuxt → packageName: "nuxt", requestedVersion: null
@@ -202,7 +206,15 @@ const repoProviderIcon = computed(() => {
202206
})
203207
204208
const homepageUrl = computed(() => {
205-
return displayVersion.value?.homepage ?? null
209+
const homepage = displayVersion.value?.homepage
210+
if (!homepage) return null
211+
212+
// Don't show homepage if it's the same as the repository URL
213+
if (repositoryUrl.value && areUrlsEquivalent(homepage, repositoryUrl.value)) {
214+
return null
215+
}
216+
217+
return homepage
206218
})
207219
208220
function normalizeGitUrl(url: string): string {
@@ -311,6 +323,17 @@ useSeoMeta({
311323
description: () => pkg.value?.description ?? '',
312324
})
313325
326+
onKeyStroke('.', () => {
327+
if (pkg.value && displayVersion.value) {
328+
router.push({
329+
name: 'code',
330+
params: {
331+
path: [pkg.value.name, 'v', displayVersion.value.version],
332+
},
333+
})
334+
}
335+
})
336+
314337
defineOgImageComponent('Package', {
315338
name: () => pkg.value?.name ?? 'Package',
316339
version: () => displayVersion.value?.version ?? '',
@@ -656,9 +679,15 @@ defineOgImageComponent('Package', {
656679
params: { path: [...pkg.name.split('/'), 'v', displayVersion.version] },
657680
}"
658681
class="link-subtle font-mono text-sm inline-flex items-center gap-1.5"
682+
aria-keyshortcuts="."
659683
>
660-
<span class="i-carbon-code w-4 h-4" aria-hidden="true" />
661684
code
685+
<kbd
686+
class="hidden sm:inline-flex items-center justify-center w-4 h-4 text-xs bg-bg-muted border border-border rounded"
687+
aria-hidden="true"
688+
>
689+
.
690+
</kbd>
662691
</NuxtLink>
663692
</li>
664693
</ul>

shared/utils/url.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { withoutProtocol, withoutTrailingSlash } from 'ufo'
2+
3+
/**
4+
* Normalize a URL for comparison by removing protocol, www prefix,
5+
* trailing slashes, hash fragments, and common git tree paths.
6+
*
7+
* Uses ufo utilities where possible, with additional handling for
8+
* www prefix and git-specific paths that ufo's isEqual doesn't cover.
9+
*/
10+
export function normalizeUrlForComparison(url: string): string {
11+
let normalized = withoutProtocol(url).toLowerCase()
12+
normalized = withoutTrailingSlash(normalized)
13+
normalized = normalized
14+
.replace(/^www\./, '')
15+
.replace(/#.*$/, '')
16+
.replace(/\/tree\/(head|main|master)(\/|$)/i, '/')
17+
return withoutTrailingSlash(normalized)
18+
}
19+
20+
/**
21+
* Check if two URLs point to the same resource.
22+
* Handles differences in protocol (http/https), www prefix,
23+
* trailing slashes, and common git branch paths.
24+
*/
25+
export function areUrlsEquivalent(url1: string, url2: string): boolean {
26+
return normalizeUrlForComparison(url1) === normalizeUrlForComparison(url2)
27+
}

test/unit/url-comparison.spec.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { areUrlsEquivalent, normalizeUrlForComparison } from '#shared/utils/url'
3+
4+
describe('normalizeUrlForComparison', () => {
5+
it('removes http protocol', () => {
6+
expect(normalizeUrlForComparison('http://github.com/foo')).toBe('github.com/foo')
7+
})
8+
9+
it('removes https protocol', () => {
10+
expect(normalizeUrlForComparison('https://github.com/foo')).toBe('github.com/foo')
11+
})
12+
13+
it('removes www prefix', () => {
14+
expect(normalizeUrlForComparison('https://www.example.com/foo')).toBe('example.com/foo')
15+
})
16+
17+
it('removes trailing slashes', () => {
18+
expect(normalizeUrlForComparison('https://github.com/foo/')).toBe('github.com/foo')
19+
})
20+
21+
it('removes hash fragments', () => {
22+
expect(normalizeUrlForComparison('https://github.com/foo#readme')).toBe('github.com/foo')
23+
})
24+
25+
it('removes /tree/head path', () => {
26+
expect(normalizeUrlForComparison('https://github.com/foo/bar/tree/head')).toBe(
27+
'github.com/foo/bar',
28+
)
29+
})
30+
31+
it('removes /tree/main path', () => {
32+
expect(normalizeUrlForComparison('https://github.com/foo/bar/tree/main')).toBe(
33+
'github.com/foo/bar',
34+
)
35+
})
36+
37+
it('removes /tree/master path', () => {
38+
expect(normalizeUrlForComparison('https://github.com/foo/bar/tree/master')).toBe(
39+
'github.com/foo/bar',
40+
)
41+
})
42+
43+
it('removes /tree/HEAD/ with trailing content', () => {
44+
expect(normalizeUrlForComparison('https://github.com/foo/bar/tree/HEAD/packages/core')).toBe(
45+
'github.com/foo/bar/packages/core',
46+
)
47+
})
48+
49+
it('lowercases the URL', () => {
50+
expect(normalizeUrlForComparison('https://GitHub.com/Foo/Bar')).toBe('github.com/foo/bar')
51+
})
52+
})
53+
54+
describe('areUrlsEquivalent', () => {
55+
it('returns true for identical URLs', () => {
56+
expect(areUrlsEquivalent('https://github.com/foo', 'https://github.com/foo')).toBe(true)
57+
})
58+
59+
it('returns true for URLs with different protocols', () => {
60+
expect(areUrlsEquivalent('http://github.com/foo', 'https://github.com/foo')).toBe(true)
61+
})
62+
63+
it('returns true for URLs with/without www', () => {
64+
expect(areUrlsEquivalent('https://www.example.com', 'https://example.com')).toBe(true)
65+
})
66+
67+
it('returns true for URLs with/without trailing slash', () => {
68+
expect(areUrlsEquivalent('https://github.com/foo/', 'https://github.com/foo')).toBe(true)
69+
})
70+
71+
it('returns true for repo URL vs homepage with tree/HEAD', () => {
72+
expect(
73+
areUrlsEquivalent(
74+
'https://github.com/nuxt/nuxt/tree/HEAD/packages/nuxt',
75+
'https://github.com/nuxt/nuxt/packages/nuxt',
76+
),
77+
).toBe(true)
78+
})
79+
80+
it('returns true for repo URL vs homepage with tree/main', () => {
81+
expect(
82+
areUrlsEquivalent('https://github.com/foo/bar', 'https://github.com/foo/bar/tree/main'),
83+
).toBe(true)
84+
})
85+
86+
it('returns false for different repos', () => {
87+
expect(areUrlsEquivalent('https://github.com/foo/bar', 'https://github.com/foo/baz')).toBe(
88+
false,
89+
)
90+
})
91+
92+
it('returns false for different domains', () => {
93+
expect(areUrlsEquivalent('https://github.com/foo', 'https://gitlab.com/foo')).toBe(false)
94+
})
95+
96+
it('returns false for repo URL vs separate homepage', () => {
97+
expect(areUrlsEquivalent('https://github.com/nuxt/nuxt', 'https://nuxt.com')).toBe(false)
98+
})
99+
})

0 commit comments

Comments
 (0)