Skip to content

Commit 397c8a5

Browse files
authored
Merge branch 'main' into perf/ofetch-timeout
2 parents 31ae154 + 9aa343a commit 397c8a5

92 files changed

Lines changed: 1221 additions & 256 deletions

File tree

Some content is hidden

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

.gitattributes

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
* text eol=lf
1+
* text=auto eol=lf

.github/workflows/autofix.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ jobs:
3434
- name: 📦 Install browsers
3535
run: pnpm playwright install
3636

37+
- name: 🌐 Compare translations
38+
run: pnpm i18n:check
39+
3740
- name: 🌍 Update lunaria data
3841
run: pnpm build:lunaria
3942

CONTRIBUTING.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,23 @@ To add a new locale:
284284

285285
Check [Pluralization rule callback](https://vue-i18n.intlify.dev/guide/essentials/pluralization.html#custom-pluralization) for more info.
286286

287+
### Update translation
288+
289+
We track the current progress of translations with [Lunaria](https://lunaria.dev/) on this site: https://i18n.npmx.dev/
290+
If you see any outdated translations in your language, feel free to update the keys to match then English version.
291+
292+
In order to make sure you have everything up-to-date, you can run:
293+
294+
```bash
295+
pnpm i18n:check <country-code>
296+
```
297+
298+
For example to check if all Japanese translation keys are up-to-date, run:
299+
300+
```bash
301+
pnpm i18n:check ja-JP
302+
```
303+
287304
#### Country variants (advanced)
288305

289306
Most languages only need a single locale file. Country variants are only needed when you want to support regional differences (e.g., `es-ES` for Spain vs `es-419` for Latin America).

app/app.vue

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const { locale, locales } = useI18n()
1010
initPreferencesOnPrehydrate()
1111
1212
const isHomepage = computed(() => route.name === 'index')
13+
const showKbdHints = shallowRef(false)
1314
1415
const localeMap = locales.value.reduce(
1516
(acc, l) => {
@@ -21,8 +22,9 @@ const localeMap = locales.value.reduce(
2122
2223
useHead({
2324
htmlAttrs: {
24-
lang: () => locale.value,
25-
dir: () => localeMap[locale.value] ?? 'ltr',
25+
'lang': () => locale.value,
26+
'dir': () => localeMap[locale.value] ?? 'ltr',
27+
'data-kbd-hints': () => showKbdHints.value,
2628
},
2729
titleTemplate: titleChunk => {
2830
return titleChunk ? titleChunk : 'npmx - Better npm Package Browser'
@@ -33,16 +35,16 @@ if (import.meta.server) {
3335
setJsonLd(createWebSiteSchema())
3436
}
3537
36-
// Global keyboard shortcut: "/" focuses search or navigates to search page
38+
// Global keyboard shortcut:
39+
// "/" focuses search or navigates to search page
40+
// "?" highlights all keyboard shortcut elements
3741
function handleGlobalKeydown(e: KeyboardEvent) {
3842
const target = e.target as HTMLElement
3943
4044
const isEditableTarget =
4145
target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable
4246
43-
if (isEditableTarget) {
44-
return
45-
}
47+
if (isEditableTarget) return
4648
4749
if (e.key === '/') {
4850
e.preventDefault()
@@ -59,15 +61,26 @@ function handleGlobalKeydown(e: KeyboardEvent) {
5961
6062
router.push('/search')
6163
}
64+
65+
if (e.key === '?') {
66+
e.preventDefault()
67+
showKbdHints.value = true
68+
}
69+
}
70+
71+
function handleGlobalKeyup() {
72+
showKbdHints.value = false
6273
}
6374
6475
if (import.meta.client) {
6576
useEventListener(document, 'keydown', handleGlobalKeydown)
77+
useEventListener(document, 'keyup', handleGlobalKeyup)
6678
}
6779
</script>
6880

6981
<template>
7082
<div class="min-h-screen flex flex-col bg-bg text-fg">
83+
<NuxtPwaAssets />
7184
<a href="#main-content" class="skip-link font-mono">{{ $t('common.skip_link') }}</a>
7285

7386
<AppHeader :show-logo="!isHomepage" />
@@ -81,3 +94,25 @@ if (import.meta.client) {
8194
<ScrollToTop />
8295
</div>
8396
</template>
97+
98+
<style>
99+
/* Keyboard shortcut highlight on "?" key press */
100+
kbd {
101+
position: relative;
102+
}
103+
104+
kbd::before {
105+
content: '';
106+
position: absolute;
107+
inset: 0;
108+
border-radius: inherit;
109+
box-shadow: 0 0 4px 2px var(--accent);
110+
opacity: 0;
111+
transition: opacity 200ms ease-out;
112+
pointer-events: none;
113+
}
114+
115+
html[data-kbd-hints='true'] kbd::before {
116+
opacity: 1;
117+
}
118+
</style>

app/assets/main.css

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -541,12 +541,6 @@ input[type='search']::-webkit-search-results-decoration {
541541
appearance: none;
542542
}
543543

544-
/* View transition for logo (hero -> header) */
545-
.hero-logo,
546-
.header-logo {
547-
view-transition-name: site-logo;
548-
}
549-
550544
/* Disable the default fade transition on page navigation */
551545
::view-transition-old(root),
552546
::view-transition-new(root) {
@@ -555,9 +549,7 @@ input[type='search']::-webkit-search-results-decoration {
555549

556550
/* Customize the view transition animations for specific elements */
557551
::view-transition-old(search-box),
558-
::view-transition-new(search-box),
559-
::view-transition-old(site-logo),
560-
::view-transition-new(site-logo) {
552+
::view-transition-new(search-box) {
561553
animation-duration: 0.3s;
562554
animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
563555
}

app/components/AnnounceTooltip.vue

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script setup lang="ts">
2+
const props = defineProps<{
3+
/** Tooltip text */
4+
text: string
5+
/** Position: 'top' | 'bottom' | 'left' | 'right' */
6+
position?: 'top' | 'bottom' | 'left' | 'right'
7+
/** is tooltip visible */
8+
isVisible: boolean
9+
}>()
10+
</script>
11+
12+
<template>
13+
<BaseTooltip :text :isVisible :position :tooltip-attr="{ 'aria-live': 'polite' }"
14+
><slot
15+
/></BaseTooltip>
16+
</template>

app/components/AppFooter.vue

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1+
<script setup lang="ts">
2+
const route = useRoute()
3+
const isHome = computed(() => route.name === 'index')
4+
</script>
5+
16
<template>
27
<footer class="border-t border-border mt-auto">
38
<div class="container py-3 sm:py-8 flex flex-col gap-2 sm:gap-4 text-fg-subtle text-sm">
49
<div
510
class="flex flex-col sm:flex-row items-center sm:items-baseline justify-between gap-2 sm:gap-4"
611
>
7-
<p class="font-mono text-balance m-0 hidden sm:block">{{ $t('tagline') }}</p>
12+
<div>
13+
<p class="font-mono text-balance m-0 hidden sm:block">{{ $t('tagline') }}</p>
14+
<BuildEnvironment v-if="!isHome" footer />
15+
</div>
816
<div class="flex items-center gap-3 sm:gap-6">
917
<NuxtLink
1018
to="/about"

app/components/AppHeader.vue

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const { isConnected, npmUser } = useConnector()
1212
1313
const router = useRouter()
1414
15-
const showFullSearch = ref(false)
15+
const showFullSearch = shallowRef(false)
1616
1717
onKeyStroke(',', e => {
1818
// Don't trigger if user is typing in an input
@@ -34,14 +34,24 @@ onKeyStroke(',', e => {
3434
>
3535
<!-- Start: Logo -->
3636
<div :class="{ 'hidden sm:block': showFullSearch }" class="flex-shrink-0">
37-
<NuxtLink
38-
v-if="showLogo"
39-
to="/"
40-
:aria-label="$t('header.home')"
41-
class="header-logo font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 focus-ring rounded"
42-
>
43-
<span class="text-accent"><span class="-tracking-0.2em">.</span>/</span>npmx
44-
</NuxtLink>
37+
<div v-if="showLogo">
38+
<NuxtLink
39+
to="/"
40+
:aria-label="$t('header.home')"
41+
dir="ltr"
42+
class="inline-flex items-center gap-2 header-logo font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 focus-ring rounded"
43+
>
44+
<img
45+
aria-hidden="true"
46+
:alt="$t('alt_logo')"
47+
src="/logo.svg"
48+
width="96"
49+
height="96"
50+
class="w-8 h-8 rounded-lg"
51+
/>
52+
<span>npmx</span>
53+
</NuxtLink>
54+
</div>
4555
<!-- Spacer when logo is hidden -->
4656
<span v-else class="w-1" />
4757
</div>

app/components/AppTooltip.vue

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,6 @@ const props = defineProps<{
99
const isVisible = shallowRef(false)
1010
const tooltipId = useId()
1111
12-
const positionClasses: Record<string, string> = {
13-
top: 'bottom-full inset-is-1/2 -translate-x-1/2 mb-1',
14-
bottom: 'top-full inset-is-0 mt-1',
15-
left: 'inset-ie-full top-1/2 -translate-y-1/2 me-2',
16-
right: 'inset-is-full top-1/2 -translate-y-1/2 ms-2',
17-
}
18-
19-
const tooltipPosition = computed(() => positionClasses[props.position || 'bottom'])
20-
2112
function show() {
2213
isVisible.value = true
2314
}
@@ -28,31 +19,16 @@ function hide() {
2819
</script>
2920

3021
<template>
31-
<div
32-
class="relative inline-flex"
33-
:aria-describedby="isVisible ? tooltipId : undefined"
22+
<BaseTooltip
23+
:text
24+
:isVisible
25+
:position
26+
:tooltip-attr="{ role: 'tooltip', id: tooltipId }"
3427
@mouseenter="show"
3528
@mouseleave="hide"
3629
@focusin="show"
3730
@focusout="hide"
38-
>
39-
<slot />
40-
41-
<Transition
42-
enter-active-class="transition-opacity duration-150 motion-reduce:transition-none"
43-
leave-active-class="transition-opacity duration-100 motion-reduce:transition-none"
44-
enter-from-class="opacity-0"
45-
leave-to-class="opacity-0"
46-
>
47-
<div
48-
v-if="isVisible"
49-
:id="tooltipId"
50-
role="tooltip"
51-
class="absolute px-2 py-1 font-mono text-xs text-fg bg-bg-elevated border border-border rounded shadow-lg whitespace-nowrap z-[100] pointer-events-none"
52-
:class="tooltipPosition"
53-
>
54-
{{ text }}
55-
</div>
56-
</Transition>
57-
</div>
31+
:aria-describedby="isVisible ? tooltipId : undefined"
32+
><slot
33+
/></BaseTooltip>
5834
</template>

app/components/AuthButton.client.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
const showModal = ref(false)
2+
const showModal = shallowRef(false)
33
const { user } = useAtproto()
44
</script>
55

0 commit comments

Comments
 (0)