Skip to content

Commit 60575ca

Browse files
committed
Merge branch 'main' into fix/issue-1323
2 parents dcda67f + 31033e7 commit 60575ca

File tree

102 files changed

+7702
-617
lines changed

Some content is hidden

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

102 files changed

+7702
-617
lines changed

.storybook/main.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ const config = {
88
backgrounds: false,
99
},
1010
async viteFinal(config) {
11+
config.plugins ??= []
12+
13+
config.plugins.push({
14+
name: 'ignore-internals',
15+
transform(_, id) {
16+
if (id.includes('/app/pages/blog/') && id.endsWith('.md')) {
17+
return 'export default {}'
18+
}
19+
},
20+
})
1121
// Replace the built-in vue-docgen plugin with a fault-tolerant version.
1222
// vue-docgen-api can crash on components that import types from other
1323
// .vue files (it tries to parse the SFC with @babel/parser as plain TS).

CONTRIBUTING.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,11 @@ shared/ # Shared between app and server
179179
└── types/ # TypeScript type definitions
180180
181181
cli/ # Local connector CLI (separate workspace)
182+
182183
test/ # Vitest tests
183184
├── unit/ # Unit tests (*.spec.ts)
184-
── nuxt/ # Nuxt component tests
185-
tests/ # Playwright E2E tests
185+
── nuxt/ # Nuxt component tests
186+
└── e2e/ # Playwright E2E tests
186187
```
187188

188189
> [!TIP]
@@ -465,6 +466,7 @@ The following scripts help manage translation files. `en.json` is the reference
465466
| `pnpm i18n:check:fix [locale]` | Same as check, but adds missing keys to other locales with English placeholders. |
466467
| `pnpm i18n:report` | Audits translation keys against code usage in `.vue` and `.ts` files. Reports missing keys (used in code but not in locale), unused keys (in locale but not in code), and dynamic keys. |
467468
| `pnpm i18n:report:fix` | Removes unused keys from `en.json` and all other locale files. |
469+
| `pnpm i18n:schema` | Generates a JSON Schema from `en.json` at `i18n/schema.json`. Locale files reference this schema for IDE validation and autocompletion. |
468470

469471
### Adding a new locale
470472

app/app.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ if (import.meta.client) {
129129

130130
<AppHeader :show-logo="!isHomepage" />
131131

132+
<NuxtRouteAnnouncer v-slot="{ message }">
133+
{{ route.name === 'search' ? `${$t('search.title_packages')} - npmx` : message }}
134+
</NuxtRouteAnnouncer>
135+
132136
<div id="main-content" class="flex-1 flex flex-col" tabindex="-1">
133137
<NuxtPage />
134138
</div>

app/assets/logos/sponsors/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export const SPONSORS = [
3737
dark: LogoVlt,
3838
light: LogoVltLight,
3939
},
40-
normalisingIndent: '0.25rem',
40+
normalisingIndent: '0.875rem',
4141
url: 'https://vlt.sh/',
4242
},
4343
]
Lines changed: 12 additions & 1 deletion
Loading

app/assets/logos/sponsors/vlt.svg

Lines changed: 12 additions & 1 deletion
Loading

app/components/AppFooter.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ const closeModal = () => modalRef.value?.close?.()
2525
<LinkBase :to="{ name: 'about' }">
2626
{{ $t('footer.about') }}
2727
</LinkBase>
28+
<LinkBase :to="{ name: 'blog' }">
29+
{{ $t('footer.blog') }}
30+
</LinkBase>
2831
<LinkBase :to="{ name: 'privacy' }">
2932
{{ $t('privacy_policy.title') }}
3033
</LinkBase>

app/components/AppHeader.vue

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ const mobileLinks = computed<NavigationConfigWithGroups>(() => [
5959
external: false,
6060
iconClass: 'i-lucide:info',
6161
},
62+
{
63+
name: 'Blog',
64+
label: $t('footer.blog'),
65+
to: { name: 'blog' },
66+
type: 'link',
67+
external: false,
68+
iconClass: 'i-lucide:notebook-pen',
69+
},
6270
{
6371
name: 'Privacy Policy',
6472
label: $t('privacy_policy.title'),

app/components/AuthorAvatar.vue

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<script setup lang="ts">
2+
import type { ResolvedAuthor } from '#shared/schemas/blog'
3+
4+
const props = defineProps<{
5+
author: Partial<ResolvedAuthor> & Pick<ResolvedAuthor, 'name' | 'avatar'>
6+
size?: 'sm' | 'md' | 'lg'
7+
}>()
8+
9+
const sizeClasses = computed(() => {
10+
switch (props.size ?? 'md') {
11+
case 'sm':
12+
return 'w-8 h-8 text-sm'
13+
case 'lg':
14+
return 'w-12 h-12 text-xl'
15+
default:
16+
return 'w-10 h-10 text-lg'
17+
}
18+
})
19+
20+
const initials = computed(() =>
21+
props.author.name
22+
.split(' ')
23+
.map(n => n[0])
24+
.join('')
25+
.toUpperCase()
26+
.slice(0, 2),
27+
)
28+
</script>
29+
30+
<template>
31+
<div
32+
class="shrink-0 flex items-center justify-center border border-border rounded-full bg-bg-muted overflow-hidden"
33+
:class="[sizeClasses]"
34+
>
35+
<img
36+
v-if="author.avatar"
37+
:src="author.avatar"
38+
:alt="author.name"
39+
class="w-full h-full object-cover"
40+
/>
41+
<span v-else class="text-fg-subtle font-mono" aria-hidden="true">
42+
{{ initials }}
43+
</span>
44+
</div>
45+
</template>

app/components/AuthorList.vue

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<script setup lang="ts">
2+
import type { Author } from '#shared/schemas/blog'
3+
4+
const props = defineProps<{
5+
authors: Author[]
6+
variant?: 'compact' | 'expanded'
7+
}>()
8+
9+
const { resolvedAuthors } = useBlueskyAuthorProfiles(props.authors)
10+
</script>
11+
12+
<template>
13+
<!-- Expanded variant: vertical list with larger avatars -->
14+
<div v-if="variant === 'expanded'" class="flex flex-wrap items-center gap-4">
15+
<div v-for="author in resolvedAuthors" :key="author.name" class="flex items-center gap-2">
16+
<AuthorAvatar :author="author" size="md" disable-link />
17+
<div class="flex flex-col">
18+
<span class="text-sm font-medium text-fg">{{ author.name }}</span>
19+
<a
20+
v-if="author.blueskyHandle && author.profileUrl"
21+
:href="author.profileUrl"
22+
target="_blank"
23+
rel="noopener noreferrer"
24+
:aria-label="$t('blog.author.view_profile', { name: author.name })"
25+
class="text-xs text-fg-muted hover:text-primary transition-colors"
26+
>
27+
@{{ author.blueskyHandle }}
28+
</a>
29+
</div>
30+
</div>
31+
</div>
32+
33+
<!-- Compact variant: no avatars -->
34+
<div v-else class="flex items-center gap-2 min-w-0">
35+
<div class="flex items-center">
36+
<AuthorAvatar
37+
v-for="(author, index) in resolvedAuthors"
38+
:key="author.name"
39+
:author="author"
40+
size="md"
41+
class="ring-2 ring-bg"
42+
:class="index > 0 ? '-ms-3' : ''"
43+
/>
44+
</div>
45+
<span class="text-xs text-fg-muted font-mono truncate">
46+
{{ resolvedAuthors.map(a => a.name).join(', ') }}
47+
</span>
48+
</div>
49+
</template>

0 commit comments

Comments
 (0)