Skip to content

Commit 453ad71

Browse files
authored
Merge branch 'main' into tobbe-feat-gh-contrib-graph
2 parents 9726a65 + 859cc4f commit 453ad71

File tree

180 files changed

+5631
-955
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

180 files changed

+5631
-955
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,4 @@ file-tree-sprite.svg
4545

4646
# output
4747
.vercel
48+
.nvmrc

.node-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
24

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ npmx.dev uses [@nuxtjs/i18n](https://i18n.nuxtjs.org/) for internationalization.
416416
- All user-facing strings should use translation keys via `$t()` in templates and script
417417
- Translation files live in [`i18n/locales/`](i18n/locales) (e.g., `en-US.json`)
418418
- We use the `no_prefix` strategy (no `/en-US/` or `/fr-FR/` in URLs)
419-
- Locale preference is stored in cookies and respected on subsequent visits
419+
- Locale preference is stored in `localStorage` and respected on subsequent visits
420420

421421
### i18n commands
422422

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ We welcome contributions – please do feel free to explore the project and
155155
- [npm-userscript](https://github.com/bluwy/npm-userscript) – Browser userscript with various improvements and fixes for npmjs.com
156156
- [npm-alt](https://npm.willow.sh/) – An alternative npm package browser
157157
- [npkg.lorypelli.dev](https://npkg.lorypelli.dev/) – An alternative frontend to npm made with as little client-side JavaScript as possible
158-
- [vscode-npmx](https://github.com/npmx-dev/vscode-npmx) – VSCode extension for npmx
158+
- [vscode-npmx](https://github.com/npmx-dev/vscode-npmx) – Official VSCode extension for npmx
159+
- [vscode-open-in-npmx](https://github.com/sybers/vscode-open-in-npmx) – VSCode shortcut to open packages on npmx
159160
- [nxjt](https://nxjt.netlify.app) – npmx Jump To: Quickly navigate to npmx common webpages.
160161
- [npmx-weekly](https://npmx-weekly.trueberryless.org/) – A weekly newsletter for the npmx ecosystem. Add your own content via suggestions in the weekly PR on [GitHub](https://github.com/trueberryless-org/npmx-weekly/pulls?q=is%3Aopen+is%3Apr+label%3A%22%F0%9F%95%94+weekly+post%22).
161162
- [npmx-digest](https://npmx-digest.trueberryless.org/) – An automated news aggregation website that summarizes npmx activity from GitHub and Bluesky every 8 hours.

app/app.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ if (import.meta.client) {
121121
<template>
122122
<div class="min-h-screen flex flex-col bg-bg text-fg">
123123
<NuxtPwaAssets />
124-
<LinkBase to="#main-content" variant="button-primary" class="skip-link">{{
124+
<LinkBase to="#main-content" external variant="button-primary" class="skip-link">{{
125125
$t('common.skip_link')
126126
}}</LinkBase>
127127

app/components/AppFooter.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<script setup lang="ts">
2+
import { NPMX_DOCS_SITE } from '#shared/utils/constants'
3+
24
const route = useRoute()
35
const isHome = computed(() => route.name === 'index')
46
@@ -13,7 +15,9 @@ const showModal = () => modalRef.value?.showModal?.()
1315
class="flex flex-col sm:flex-row sm:flex-wrap items-center sm:items-baseline justify-between gap-2 sm:gap-4"
1416
>
1517
<div>
16-
<p class="font-mono text-balance m-0 hidden sm:block">{{ $t('tagline') }}</p>
18+
<p class="font-mono text-balance m-0 hidden sm:block">
19+
{{ $t('tagline') }}
20+
</p>
1721
</div>
1822
<!-- Desktop: Show all links. Mobile: Links are in MobileMenu -->
1923
<div class="hidden sm:flex items-center gap-6 min-h-11 text-xs">
@@ -92,7 +96,7 @@ const showModal = () => modalRef.value?.showModal?.()
9296
</li>
9397
</ul>
9498
</Modal>
95-
<LinkBase to="https://docs.npmx.dev">
99+
<LinkBase :to="NPMX_DOCS_SITE">
96100
{{ $t('footer.docs') }}
97101
</LinkBase>
98102
<LinkBase to="https://repo.npmx.dev">

app/components/AppHeader.vue

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { LinkBase } from '#components'
33
import type { NavigationConfig, NavigationConfigWithGroups } from '~/types'
44
import { isEditableElement } from '~/utils/input'
5+
import { NPMX_DOCS_SITE } from '#shared/utils/constants'
56
67
withDefaults(
78
defineProps<{
@@ -22,7 +23,7 @@ const desktopLinks = computed<NavigationConfig>(() => [
2223
keyshortcut: 'c',
2324
type: 'link',
2425
external: false,
25-
iconClass: 'i-carbon:compare',
26+
iconClass: 'i-lucide:git-compare',
2627
},
2728
{
2829
name: 'Settings',
@@ -31,7 +32,7 @@ const desktopLinks = computed<NavigationConfig>(() => [
3132
keyshortcut: ',',
3233
type: 'link',
3334
external: false,
34-
iconClass: 'i-carbon:settings',
35+
iconClass: 'i-lucide:settings',
3536
},
3637
])
3738
@@ -54,23 +55,23 @@ const mobileLinks = computed<NavigationConfigWithGroups>(() => [
5455
to: { name: 'about' },
5556
type: 'link',
5657
external: false,
57-
iconClass: 'i-carbon:information',
58+
iconClass: 'i-lucide:info',
5859
},
5960
{
6061
name: 'Privacy Policy',
6162
label: $t('privacy_policy.title'),
6263
to: { name: 'privacy' },
6364
type: 'link',
6465
external: false,
65-
iconClass: 'i-carbon:security',
66+
iconClass: 'i-lucide:shield-check',
6667
},
6768
{
6869
name: 'Accessibility',
6970
label: $t('a11y.title'),
7071
to: { name: 'accessibility' },
7172
type: 'link',
7273
external: false,
73-
iconClass: 'i-carbon:accessibility-alt',
74+
iconClass: 'i-custom:a11y',
7475
},
7576
],
7677
},
@@ -85,11 +86,11 @@ const mobileLinks = computed<NavigationConfigWithGroups>(() => [
8586
{
8687
name: 'Docs',
8788
label: $t('footer.docs'),
88-
href: 'https://docs.npmx.dev',
89+
href: NPMX_DOCS_SITE,
8990
target: '_blank',
9091
type: 'link',
9192
external: true,
92-
iconClass: 'i-carbon:document',
93+
iconClass: 'i-lucide:file-text',
9394
},
9495
{
9596
name: 'Source',
@@ -98,7 +99,7 @@ const mobileLinks = computed<NavigationConfigWithGroups>(() => [
9899
target: '_blank',
99100
type: 'link',
100101
external: true,
101-
iconClass: 'i-carbon:logo-github',
102+
iconClass: 'i-simple-icons:github',
102103
},
103104
{
104105
name: 'Social',
@@ -116,14 +117,15 @@ const mobileLinks = computed<NavigationConfigWithGroups>(() => [
116117
target: '_blank',
117118
type: 'link',
118119
external: true,
119-
iconClass: 'i-carbon:chat',
120+
iconClass: 'i-lucide:message-circle',
120121
},
121122
],
122123
},
123124
])
124125
125126
const showFullSearch = shallowRef(false)
126-
const showMobileMenu = shallowRef(false)
127+
const showMobileMenu = shallowRef(true)
128+
const { env } = useAppConfig().buildInfo
127129
128130
// On mobile, clicking logo+search button expands search
129131
const route = useRoute()
@@ -194,31 +196,34 @@ onKeyStroke(
194196
<div class="absolute inset-0 bg-bg/80 backdrop-blur-md" />
195197
<nav
196198
:aria-label="$t('nav.main_navigation')"
197-
class="relative container min-h-14 flex items-center gap-2 z-1"
198-
:class="isOnHomePage ? 'justify-end' : 'justify-between'"
199+
class="relative container min-h-14 flex items-center gap-2 z-1 justify-end"
199200
>
200-
<!-- Mobile: Logo + search button (expands search, doesn't navigate) -->
201-
<button
201+
<!-- Mobile: Logo (navigates home) -->
202+
<NuxtLink
202203
v-if="!isSearchExpanded && !isOnHomePage"
203-
type="button"
204-
class="sm:hidden flex-shrink-0 inline-flex items-center gap-2 font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 rounded"
205-
:aria-label="$t('nav.tap_to_search')"
206-
@click="expandMobileSearch"
204+
to="/"
205+
:aria-label="$t('header.home')"
206+
class="sm:hidden flex-shrink-0 font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 focus-ring"
207207
>
208208
<AppLogo class="w-8 h-8 rounded-lg" />
209-
<span class="i-carbon:search w-4 h-4 text-fg-subtle" aria-hidden="true" />
210-
</button>
209+
</NuxtLink>
211210

212211
<!-- Desktop: Logo (navigates home) -->
213212
<div v-if="showLogo" class="hidden sm:flex flex-shrink-0 items-center">
214213
<NuxtLink
215214
:to="{ name: 'index' }"
216215
:aria-label="$t('header.home')"
217216
dir="ltr"
218-
class="inline-flex items-center gap-1 header-logo font-mono text-lg font-medium text-fg hover:text-fg/90 transition-colors duration-200 rounded"
217+
class="relative inline-flex items-center gap-1 header-logo font-mono text-lg font-medium text-fg hover:text-fg/90 transition-colors duration-200 rounded"
219218
>
220-
<AppLogo class="w-8 h-8 rounded-lg" />
221-
<span>npmx</span>
219+
<AppLogo class="w-7 h-7 rounded-lg" />
220+
<span class="pb-0.5">npmx</span>
221+
<span
222+
aria-hidden="true"
223+
class="scale-35 transform-origin-br font-mono tracking-wide text-accent absolute bottom-0.5 -inset-ie-1"
224+
>
225+
{{ env === 'release' ? 'alpha' : env }}
226+
</span>
222227
</NuxtLink>
223228
</div>
224229
<!-- Spacer when logo is hidden on desktop -->
@@ -275,14 +280,25 @@ onKeyStroke(
275280
<HeaderAccountMenu />
276281
</div>
277282

283+
<!-- Mobile: Search button (expands search) -->
284+
<ButtonBase
285+
type="button"
286+
class="sm:hidden ms-auto"
287+
:aria-label="$t('nav.tap_to_search')"
288+
:aria-expanded="showMobileMenu"
289+
@click="expandMobileSearch"
290+
v-if="!isSearchExpanded && !isOnHomePage"
291+
classicon="i-lucide:search"
292+
/>
293+
278294
<!-- Mobile: Menu button (always visible, click to open menu) -->
279295
<ButtonBase
280296
type="button"
281297
class="sm:hidden"
282298
:aria-label="$t('nav.open_menu')"
283299
:aria-expanded="showMobileMenu"
284300
@click="showMobileMenu = !showMobileMenu"
285-
classicon="i-carbon:menu"
301+
classicon="i-lucide:menu"
286302
/>
287303
</nav>
288304

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<script setup lang="ts">
2+
import { BLUESKY_API, BSKY_POST_AT_URI_REGEX } from '#shared/utils/constants'
3+
4+
const props = defineProps<{
5+
/** AT URI of the post, e.g. at://did:plc:.../app.bsky.feed.post/... */
6+
uri: string
7+
}>()
8+
9+
interface PostAuthor {
10+
did: string
11+
handle: string
12+
displayName?: string
13+
avatar?: string
14+
}
15+
16+
interface EmbedImage {
17+
thumb: string
18+
fullsize: string
19+
alt: string
20+
aspectRatio?: { width: number; height: number }
21+
}
22+
23+
interface BlueskyPost {
24+
uri: string
25+
author: PostAuthor
26+
record: { text: string; createdAt: string }
27+
embed?: { $type: string; images?: EmbedImage[] }
28+
likeCount?: number
29+
replyCount?: number
30+
repostCount?: number
31+
}
32+
33+
const postUrl = computed(() => {
34+
const match = props.uri.match(BSKY_POST_AT_URI_REGEX)
35+
if (!match) return null
36+
const [, did, rkey] = match
37+
return `https://bsky.app/profile/${did}/post/${rkey}`
38+
})
39+
40+
const { data: post, status } = useAsyncData(
41+
`bsky-post-${props.uri}`,
42+
async (): Promise<BlueskyPost | null> => {
43+
const response = await $fetch<{ posts: BlueskyPost[] }>(
44+
`${BLUESKY_API}/xrpc/app.bsky.feed.getPosts`,
45+
{ query: { uris: props.uri } },
46+
)
47+
return response.posts[0] ?? null
48+
},
49+
{ lazy: true, server: false },
50+
)
51+
</script>
52+
53+
<template>
54+
<div
55+
v-if="status === 'pending'"
56+
class="rounded-lg border border-border bg-bg-subtle p-6 text-center text-fg-subtle text-sm"
57+
>
58+
<span class="i-svg-spinners:90-ring-with-bg h-5 w-5 inline-block" />
59+
</div>
60+
61+
<a
62+
v-else-if="post"
63+
:href="postUrl ?? '#'"
64+
target="_blank"
65+
rel="noopener noreferrer"
66+
class="block rounded-lg border border-border bg-bg-subtle p-4 sm:p-5 no-underline hover:border-border-hover transition-colors duration-200"
67+
>
68+
<!-- Author row -->
69+
<div class="flex items-center gap-3 mb-3">
70+
<img
71+
v-if="post.author.avatar"
72+
:src="`${post.author.avatar}?size=48`"
73+
:alt="post.author.displayName || post.author.handle"
74+
width="40"
75+
height="40"
76+
class="w-10 h-10 rounded-full"
77+
loading="lazy"
78+
/>
79+
<div class="min-w-0">
80+
<div class="font-medium text-fg truncate">
81+
{{ post.author.displayName || post.author.handle }}
82+
</div>
83+
<div class="text-sm text-fg-subtle truncate">@{{ post.author.handle }}</div>
84+
</div>
85+
<span
86+
class="i-simple-icons:bluesky w-5 h-5 text-fg-subtle ms-auto shrink-0"
87+
aria-hidden="true"
88+
/>
89+
</div>
90+
91+
<!-- Post text -->
92+
<p class="text-fg-muted whitespace-pre-wrap leading-relaxed mb-3">{{ post.record.text }}</p>
93+
94+
<!-- Embedded images -->
95+
<template v-if="post.embed?.images?.length">
96+
<img
97+
v-for="(img, i) in post.embed.images"
98+
:key="i"
99+
:src="img.fullsize"
100+
:alt="img.alt"
101+
class="w-full mb-3 rounded-lg object-cover"
102+
:style="
103+
img.aspectRatio
104+
? { aspectRatio: `${img.aspectRatio.width}/${img.aspectRatio.height}` }
105+
: undefined
106+
"
107+
loading="lazy"
108+
/>
109+
</template>
110+
111+
<!-- Timestamp + engagement -->
112+
<div class="flex items-center gap-4 text-sm text-fg-subtle">
113+
<DateTime :datetime="post.record.createdAt" date-style="medium" />
114+
<span v-if="post.likeCount" class="flex items-center gap-1">
115+
<span class="i-lucide:heart w-3.5 h-3.5" aria-hidden="true" />
116+
{{ post.likeCount }}
117+
</span>
118+
<span v-if="post.repostCount" class="flex items-center gap-1">
119+
<span class="i-lucide:repeat w-3.5 h-3.5" aria-hidden="true" />
120+
{{ post.repostCount }}
121+
</span>
122+
<span v-if="post.replyCount" class="flex items-center gap-1">
123+
<span class="i-lucide:message-circle w-3.5 h-3.5" aria-hidden="true" />
124+
{{ post.replyCount }}
125+
</span>
126+
</div>
127+
</a>
128+
</template>

app/components/CallToAction.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ const socialLinks = computed(() => [
33
{
44
id: 'github',
55
href: 'https://repo.npmx.dev',
6-
icon: 'i-carbon:logo-github',
6+
icon: 'i-simple-icons:github',
77
titleKey: $t('about.get_involved.contribute.title'),
88
descriptionKey: $t('about.get_involved.contribute.description'),
99
ctaKey: $t('about.get_involved.contribute.cta'),
1010
},
1111
{
1212
id: 'discord',
1313
href: 'https://chat.npmx.dev',
14-
icon: 'i-carbon:chat',
14+
icon: 'i-lucide:message-circle',
1515
titleKey: $t('about.get_involved.community.title'),
1616
descriptionKey: $t('about.get_involved.community.description'),
1717
ctaKey: $t('about.get_involved.community.cta'),
@@ -55,7 +55,7 @@ const socialLinks = computed(() => [
5555
class="text-sm text-fg-muted group-hover:text-fg inline-flex items-center gap-1 mt-auto focus-visible:outline-none"
5656
>
5757
{{ link.ctaKey }}
58-
<span class="i-carbon:arrow-right rtl-flip w-3 h-3" aria-hidden="true" />
58+
<span class="i-lucide:arrow-right rtl-flip w-3 h-3" aria-hidden="true" />
5959
<span class="absolute z-0 inset-0" aria-hidden="true" />
6060
</a>
6161
</div>

app/components/Code/FileTree.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ watch(
5959
:aria-pressed="isNodeActive(node)"
6060
:style="{ paddingLeft: `${depth * 12 + 12}px` }"
6161
@click="toggleDir(node.path)"
62-
:classicon="isExpanded(node.path) ? 'i-carbon:chevron-down' : 'i-carbon:chevron-right'"
62+
:classicon="isExpanded(node.path) ? 'i-lucide:chevron-down' : 'i-lucide:chevron-right'"
6363
>
6464
<svg
6565
class="size-[1em] me-1 shrink-0"

0 commit comments

Comments
 (0)