Skip to content

Commit 167753f

Browse files
authored
Merge branch 'main' into renovate/nuxt
2 parents ecc6942 + 791ce70 commit 167753f

File tree

9 files changed

+196
-30
lines changed

9 files changed

+196
-30
lines changed

.storybook/handlers.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,68 @@ export const pdsUsersHandler = http.get('/api/atproto/pds-users', () => {
9191
},
9292
])
9393
})
94+
95+
export const i18nStatusHandler = http.get('/lunaria/status.json', () => {
96+
return HttpResponse.json({
97+
generatedAt: '2026-01-22T10:07:07.000Z',
98+
sourceLocale: {
99+
lang: 'en',
100+
label: 'English',
101+
totalKeys: 500,
102+
},
103+
locales: [
104+
{
105+
lang: 'en-GB',
106+
label: 'English (UK)',
107+
dir: 'ltr',
108+
totalKeys: 500,
109+
completedKeys: 423,
110+
percentComplete: 84,
111+
missingKeys: [
112+
'settings.background_themes.label',
113+
'settings.enable_graph_pulse_loop',
114+
'settings.enable_graph_pulse_loop_description',
115+
'settings.data_source.algolia_description',
116+
'settings.data_source.npm_description',
117+
'i18n.contribute_hint',
118+
'i18n.copy_keys',
119+
],
120+
githubEditUrl: 'https://github.com/npmx-dev/npmx.dev/edit/main/i18n/locales/en-GB.json',
121+
githubHistoryUrl:
122+
'https://github.com/npmx-dev/npmx.dev/commits/main/i18n/locales/en-GB.json',
123+
},
124+
{
125+
lang: 'fr-FR',
126+
label: 'Français',
127+
dir: 'ltr',
128+
totalKeys: 500,
129+
completedKeys: 423,
130+
percentComplete: 84,
131+
missingKeys: [
132+
'settings.background_themes.label',
133+
'settings.enable_graph_pulse_loop',
134+
'settings.enable_graph_pulse_loop_description',
135+
'settings.data_source.algolia_description',
136+
'settings.data_source.npm_description',
137+
'i18n.contribute_hint',
138+
'i18n.copy_keys',
139+
],
140+
githubEditUrl: 'https://github.com/npmx-dev/npmx.dev/edit/main/i18n/locales/fr-FR.json',
141+
githubHistoryUrl:
142+
'https://github.com/npmx-dev/npmx.dev/commits/main/i18n/locales/fr-FR.json',
143+
},
144+
{
145+
lang: 'de-DE',
146+
label: 'Deutsch',
147+
dir: 'ltr',
148+
totalKeys: 500,
149+
completedKeys: 500,
150+
percentComplete: 100,
151+
missingKeys: [],
152+
githubEditUrl: 'https://github.com/npmx-dev/npmx.dev/edit/main/i18n/locales/de-DE.json',
153+
githubHistoryUrl:
154+
'https://github.com/npmx-dev/npmx.dev/commits/main/i18n/locales/de-DE.json',
155+
},
156+
],
157+
})
158+
})

.storybook/preview-head.html

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,19 @@
6161
background-color: var(--bg, oklch(0.171 0 0)) !important;
6262
}
6363
</style>
64+
<script>
65+
// related: https://github.com/npmx-dev/npmx.dev/blob/1431d24be555bca5e1ae6264434d49ca15173c43/test/nuxt/setup.ts#L12-L26
66+
// Stub Nuxt specific globals
67+
// @nuxtjs/color-mode's plugin.client.js reads window[globalName] at module
68+
// evaluation time — before any Storybook setup() callback runs — so the
69+
// global must exist in the HTML head, not in preview.ts.
70+
window.__NUXT_COLOR_MODE__ ??= {
71+
preference: 'system',
72+
value: 'dark',
73+
getColorScheme: function () {
74+
return 'dark'
75+
},
76+
addColorScheme: function () {},
77+
removeColorScheme: function () {},
78+
}
79+
</script>

.storybook/preview.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,6 @@ import npmxDark from './theme'
1010

1111
initialize()
1212

13-
// related: https://github.com/npmx-dev/npmx.dev/blob/1431d24be555bca5e1ae6264434d49ca15173c43/test/nuxt/setup.ts#L12-L26
14-
// Stub Nuxt specific globals
15-
// @ts-expect-error - dynamic global name
16-
globalThis['__NUXT_COLOR_MODE__'] ??= {
17-
preference: 'system',
18-
value: 'dark',
19-
getColorScheme: fn(() => 'dark'),
20-
addColorScheme: fn(),
21-
removeColorScheme: fn(),
22-
}
2313
// @ts-expect-error - dynamic global name
2414
globalThis.defineOgImageComponent = fn()
2515

app/components/Package/TrendsChart.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1540,7 +1540,7 @@ const chartConfig = computed<VueUiXyConfig>(() => {
15401540
const rows = items
15411541
.map((d: Record<string, any>) => {
15421542
const label = String(d?.name ?? '').trim()
1543-
const raw = Number(d?.value ?? 0)
1543+
const raw = Math.round(Number(d?.value ?? 0))
15441544
const v = compactNumberFormatter.value.format(Number.isFinite(raw) ? raw : 0)
15451545
15461546
if (!hasMultipleItems) {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type { ShallowRef } from 'vue'
2+
3+
type UseClipboardAsyncReturn = {
4+
copy: () => void
5+
copied: ShallowRef<boolean>
6+
}
7+
8+
type UseClipboardAsyncOptions = {
9+
copiedDuring: number
10+
}
11+
12+
export function useClipboardAsync(
13+
fn: () => Promise<string>,
14+
options?: UseClipboardAsyncOptions,
15+
): UseClipboardAsyncReturn {
16+
const copied = shallowRef(false)
17+
const timeout = useTimeoutFn(() => (copied.value = false), options?.copiedDuring ?? 0, {
18+
immediate: false,
19+
})
20+
21+
async function copy() {
22+
const asyncClipboard = new ClipboardItem({
23+
'text/plain': fn().then(text => {
24+
return new Blob([text], { type: 'text/plain' })
25+
}),
26+
})
27+
28+
try {
29+
await navigator.clipboard.write([asyncClipboard])
30+
copied.value = true
31+
timeout.start()
32+
} catch {
33+
copied.value = false
34+
}
35+
}
36+
37+
return {
38+
copy,
39+
copied,
40+
}
41+
}

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

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -96,26 +96,22 @@ const {
9696
)
9797
9898
//copy README file as Markdown
99-
const { copied: copiedReadme, copy: copyReadme } = useClipboard({
100-
source: () => '',
101-
copiedDuring: 2000,
102-
})
99+
const { copied: copiedReadme, copy: copyReadme } = useClipboardAsync(
100+
async () => {
101+
await fetchReadmeMarkdown()
102+
return readmeMarkdownData.value?.markdown ?? ''
103+
},
104+
{
105+
copiedDuring: 2000,
106+
},
107+
)
103108
104109
function prefetchReadmeMarkdown() {
105110
if (readmeMarkdownStatus.value === 'idle') {
106111
fetchReadmeMarkdown()
107112
}
108113
}
109114
110-
async function copyReadmeHandler() {
111-
await fetchReadmeMarkdown()
112-
113-
const markdown = readmeMarkdownData.value?.markdown
114-
if (!markdown) return
115-
116-
await copyReadme(markdown)
117-
}
118-
119115
// Track active TOC item based on scroll position
120116
const tocItems = computed(() => readmeData.value?.toc ?? [])
121117
const { activeId: activeTocId } = useActiveTocItem(tocItems)
@@ -1019,7 +1015,7 @@ const showSkeleton = shallowRef(false)
10191015
<ButtonBase
10201016
@mouseenter="prefetchReadmeMarkdown"
10211017
@focus="prefetchReadmeMarkdown"
1022-
@click="copyReadmeHandler()"
1018+
@click="copyReadme"
10231019
:aria-pressed="copiedReadme"
10241020
:aria-label="
10251021
copiedReadme ? $t('common.copied') : $t('package.readme.copy_as_markdown')

app/pages/settings.stories.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import Settings from './settings.vue'
2+
import type { Meta, StoryObj } from '@storybook-vue/nuxt'
3+
import { userEvent, expect } from 'storybook/test'
4+
import { pageDecorator } from '../../.storybook/decorators'
5+
import { i18nStatusHandler } from '../../.storybook/handlers'
6+
7+
const meta = {
8+
component: Settings,
9+
globals: {
10+
locale: 'en-US',
11+
},
12+
beforeEach: () => localStorage.removeItem('npmx-settings'),
13+
parameters: {
14+
layout: 'fullscreen',
15+
msw: {
16+
handlers: [i18nStatusHandler],
17+
},
18+
},
19+
decorators: [pageDecorator],
20+
} satisfies Meta<typeof Settings>
21+
22+
export default meta
23+
type Story = StoryObj<typeof meta>
24+
25+
/** English locale (default). The Language section shows a GitHub link to help translate the site. */
26+
export const Default: Story = {}
27+
28+
export const NpmRegistryDataSource: Story = {
29+
play: async ({ canvas, step }) => {
30+
await step('Select npm registry as the data source', async () => {
31+
const select = await canvas.findByRole('combobox', { name: /data source/i })
32+
await userEvent.selectOptions(select, 'npm')
33+
await expect(select).toHaveValue('npm')
34+
})
35+
},
36+
}
37+
38+
/** Non-English locale with incomplete translations. The Language section shows `SettingsTranslationHelper` with a progress bar and list of missing translation keys. `/lunaria/status.json` is intercepted by MSW to provide mock translation status data. */
39+
export const NonEnglishTranslationHelper: Story = {
40+
globals: {
41+
locale: 'fr-FR',
42+
},
43+
}
44+
45+
/** Non-English locale without translations API response. The Language section shows a GitHub link to help translate the site. */
46+
export const WithoutTranslationHelper: Story = {
47+
globals: {
48+
locale: 'fr-FR',
49+
},
50+
parameters: {
51+
msw: {
52+
handlers: [],
53+
},
54+
},
55+
}

nuxt.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ export default defineNuxtConfig({
1515
'@vite-pwa/nuxt',
1616
'@vueuse/nuxt',
1717
'@nuxtjs/i18n',
18-
...(isStorybook ? [] : ['@nuxt/fonts', '@nuxtjs/color-mode']),
18+
'@nuxtjs/color-mode',
19+
...(isStorybook ? [] : ['@nuxt/fonts']),
1920
],
2021

2122
$test: {

server/api/registry/timeline/sizes/[...pkg].get.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { getVersions } from 'fast-npm-meta'
2+
13
const DEFAULT_LIMIT = 25
24

35
export interface TimelineSizeEntry {
@@ -39,11 +41,11 @@ export default defineCachedEventHandler(
3941
const limit = Math.max(1, Math.min(100, Number(query.limit) || DEFAULT_LIMIT))
4042

4143
try {
42-
const packument = await fetchNpmPackage(packageName)
44+
const { versions, time } = await getVersions(packageName)
4345

44-
const allVersions = Object.keys(packument.versions)
45-
.filter(v => packument.time[v])
46-
.sort((a, b) => Date.parse(packument.time[b]!) - Date.parse(packument.time[a]!))
46+
const allVersions = versions
47+
.filter(v => time[v])
48+
.sort((a, b) => Date.parse(time[b]!) - Date.parse(time[a]!))
4749

4850
const pageVersions = allVersions.slice(offset, offset + limit)
4951

0 commit comments

Comments
 (0)