Skip to content

Commit eb4d862

Browse files
ShroXdautofix-ci[bot]ghostdevv
authored
feat: add semver range filter and element title support (#2176)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Willow (GHOST) <git@willow.sh>
1 parent 5fe1486 commit eb4d862

File tree

20 files changed

+233
-230
lines changed

20 files changed

+233
-230
lines changed

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

Lines changed: 111 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const distTags = computed(() => versionSummary.value?.distTags ?? {})
4949
const versionStrings = computed(() => versionSummary.value?.versions ?? [])
5050
const versionTimes = computed(() => versionSummary.value?.time ?? {})
5151
52-
// ─── Phase 2: full metadata (loaded on first group expand) ────────────────────
52+
// ─── Phase 2: full metadata (fired automatically after phase 1 completes) ────
5353
// Fetches deprecated status, provenance, and exact times needed for version rows.
5454
5555
const fullVersionMap = shallowRef<Map<
@@ -82,7 +82,6 @@ function getVersionTime(version: string): string | undefined {
8282
// ─── Version groups ───────────────────────────────────────────────────────────
8383
8484
const expandedGroups = ref(new Set<string>())
85-
const loadingGroup = ref<string | null>(null)
8685
8786
const versionGroups = computed(() => {
8887
const byKey = new Map<string, string[]>()
@@ -101,27 +100,42 @@ const versionGroups = computed(() => {
101100
}))
102101
})
103102
104-
async function toggleGroup(groupKey: string) {
103+
const deprecatedGroupKeys = computed(() => {
104+
if (!fullVersionMap.value) return new Set<string>()
105+
const result = new Set<string>()
106+
for (const group of versionGroups.value) {
107+
if (group.versions.every(v => !!fullVersionMap.value!.get(v)?.deprecated))
108+
result.add(group.groupKey)
109+
}
110+
return result
111+
})
112+
113+
function toggleGroup(groupKey: string) {
105114
if (expandedGroups.value.has(groupKey)) {
106115
expandedGroups.value.delete(groupKey)
107-
return
116+
} else {
117+
expandedGroups.value.add(groupKey)
108118
}
109-
expandedGroups.value.add(groupKey)
110-
if (!fullVersionMap.value) {
111-
loadingGroup.value = groupKey
112-
try {
119+
}
120+
121+
watch(
122+
versionSummary,
123+
async summary => {
124+
if (summary) {
113125
await ensureFullDataLoaded()
114-
} finally {
115-
loadingGroup.value = null
116126
}
117-
}
118-
}
127+
},
128+
{ immediate: true },
129+
)
119130
120131
// ─── Version filter ───────────────────────────────────────────────────────────
121132
122133
const versionFilterInput = ref('')
123134
const versionFilter = refDebounced(versionFilterInput, 100)
124135
const isFilterActive = computed(() => versionFilter.value.trim() !== '')
136+
const isInvalidRange = computed(
137+
() => isFilterActive.value && validRange(versionFilter.value.trim()) === null,
138+
)
125139
126140
const filteredVersionSet = computed(() => {
127141
const trimmed = versionFilter.value.trim()
@@ -198,14 +212,40 @@ const flatItems = computed<FlatItem[]>(() => {
198212
<span class="text-fg-subtle shrink-0">/</span>
199213
<h1 class="text-sm text-fg-muted shrink-0">{{ $t('package.versions.page_title') }}</h1>
200214
</div>
201-
<InputBase
202-
v-model="versionFilterInput"
203-
type="text"
204-
:placeholder="$t('package.versions.version_filter_placeholder')"
205-
:aria-label="$t('package.versions.version_filter_label')"
206-
size="sm"
207-
class="w-36 sm:w-44"
208-
/>
215+
<div class="relative">
216+
<InputBase
217+
v-model="versionFilterInput"
218+
type="text"
219+
:placeholder="$t('package.versions.filter_placeholder')"
220+
:aria-label="$t('package.versions.filter_placeholder')"
221+
:aria-invalid="isInvalidRange ? 'true' : undefined"
222+
:aria-describedby="isInvalidRange ? 'version-filter-error' : undefined"
223+
autocomplete="off"
224+
size="sm"
225+
class="w-36 sm:w-64"
226+
:class="isInvalidRange ? 'pe-7 !border-red-500' : ''"
227+
/>
228+
<Transition
229+
enter-active-class="transition-all duration-150"
230+
enter-from-class="opacity-0 scale-60"
231+
leave-active-class="transition-all duration-150"
232+
leave-to-class="opacity-0 scale-60"
233+
>
234+
<TooltipApp
235+
v-if="isInvalidRange"
236+
:text="$t('package.versions.filter_invalid')"
237+
position="bottom"
238+
class="absolute end-0 inset-y-0 flex items-center pe-2"
239+
>
240+
<span
241+
id="version-filter-error"
242+
class="i-lucide:circle-alert w-3.5 h-3.5 text-red-500 block"
243+
role="img"
244+
:aria-label="$t('package.versions.filter_invalid')"
245+
/>
246+
</TooltipApp>
247+
</Transition>
248+
</div>
209249
</div>
210250
</header>
211251

@@ -230,18 +270,26 @@ const flatItems = computed<FlatItem[]>(() => {
230270
v-for="tag in latestTagRow!.tags.filter(t => t !== 'latest')"
231271
:key="tag"
232272
class="text-3xs font-semibold uppercase tracking-wide text-fg-subtle"
273+
:title="tag"
233274
>{{ tag }}</span
234275
>
235276
</div>
236277
<LinkBase
237278
:to="packageRoute(packageName, latestTagRow!.version)"
238279
class="text-2xl font-semibold tracking-tight after:absolute after:inset-0 after:content-['']"
280+
:title="latestTagRow!.version"
239281
dir="ltr"
240282
>{{ latestTagRow!.version }}</LinkBase
241283
>
242284
</div>
243-
<!-- Right: date + provenance -->
285+
<!-- Right: deprecated + date + provenance -->
244286
<div class="flex flex-col items-end gap-1.5 shrink-0 relative z-10">
287+
<span
288+
v-if="fullVersionMap?.get(latestTagRow!.version)?.deprecated"
289+
class="text-3xs font-medium text-red-700 dark:text-red-400 bg-red-100 dark:bg-red-900/30 px-1.5 py-0.5 rounded"
290+
:title="fullVersionMap!.get(latestTagRow!.version)!.deprecated"
291+
>deprecated</span
292+
>
245293
<ProvenanceBadge
246294
v-if="fullVersionMap?.get(latestTagRow!.version)?.hasProvenance"
247295
:package-name="packageName"
@@ -276,6 +324,7 @@ const flatItems = computed<FlatItem[]>(() => {
276324
v-for="tag in row.tags"
277325
:key="tag"
278326
class="text-3xs font-semibold uppercase tracking-wide text-fg-subtle"
327+
:title="tag"
279328
>{{ tag }}</span
280329
>
281330
</div>
@@ -284,30 +333,36 @@ const flatItems = computed<FlatItem[]>(() => {
284333
<LinkBase
285334
:to="packageRoute(packageName, row.version)"
286335
class="text-sm flex-1 min-w-0 after:absolute after:inset-0 after:content-['']"
336+
:title="row.version"
287337
dir="ltr"
288338
>
289339
{{ row.version }}
290340
</LinkBase>
291341

292-
<!-- Date -->
293-
<DateTime
294-
v-if="getVersionTime(row.version)"
295-
:datetime="getVersionTime(row.version)!"
296-
class="text-xs text-fg-subtle shrink-0 hidden sm:block"
297-
year="numeric"
298-
month="short"
299-
day="numeric"
300-
/>
301-
302-
<!-- Provenance -->
303-
<ProvenanceBadge
304-
v-if="fullVersionMap?.get(row.version)?.hasProvenance"
305-
:package-name="packageName"
306-
:version="row.version"
307-
compact
308-
:linked="false"
309-
class="relative z-10 shrink-0"
310-
/>
342+
<!-- Deprecated + Date + Provenance -->
343+
<div class="flex items-center gap-2 shrink-0 relative z-10">
344+
<span
345+
v-if="fullVersionMap?.get(row.version)?.deprecated"
346+
class="text-3xs font-medium text-red-700 dark:text-red-400 bg-red-100 dark:bg-red-900/30 px-1.5 py-0.5 rounded"
347+
:title="fullVersionMap!.get(row.version)!.deprecated"
348+
>deprecated</span
349+
>
350+
<DateTime
351+
v-if="getVersionTime(row.version)"
352+
:datetime="getVersionTime(row.version)!"
353+
class="text-xs text-fg-subtle hidden sm:block"
354+
year="numeric"
355+
month="short"
356+
day="numeric"
357+
/>
358+
<ProvenanceBadge
359+
v-if="fullVersionMap?.get(row.version)?.hasProvenance"
360+
:package-name="packageName"
361+
:version="row.version"
362+
compact
363+
:linked="false"
364+
/>
365+
</div>
311366
</div>
312367
</div>
313368
</section>
@@ -351,15 +406,9 @@ const flatItems = computed<FlatItem[]>(() => {
351406
<span class="w-4 h-4 flex items-center justify-center text-fg-subtle shrink-0">
352407
<Transition name="icon-swap" mode="out-in">
353408
<span
354-
v-if="loadingGroup === item.groupKey"
355-
key="loading"
356-
class="i-svg-spinners:ring-resize w-3 h-3"
357-
aria-hidden="true"
358-
/>
359-
<span
360-
v-else-if="isFilterActive"
409+
v-if="isFilterActive"
361410
key="search"
362-
class="i-lucide:search w-3 h-3 animate-searching"
411+
class="i-lucide:funnel w-3 h-3"
363412
aria-hidden="true"
364413
/>
365414
<span
@@ -372,9 +421,16 @@ const flatItems = computed<FlatItem[]>(() => {
372421
</Transition>
373422
</span>
374423
<span class="text-sm font-medium">{{ item.label }}</span>
424+
<span
425+
v-if="deprecatedGroupKeys.has(item.groupKey)"
426+
class="text-3xs font-medium text-red-700 dark:text-red-400 bg-red-100 dark:bg-red-900/30 px-1.5 py-0.5 rounded"
427+
>deprecated</span
428+
>
375429
<span class="text-xs text-fg-subtle">({{ item.versions.length }})</span>
376430
<span class="ms-auto flex items-center gap-3 shrink-0">
377-
<span class="text-xs text-fg-muted" dir="ltr">{{ item.versions[0] }}</span>
431+
<span class="text-xs text-fg-muted" :title="item.versions[0]" dir="ltr">{{
432+
item.versions[0]
433+
}}</span>
378434
<DateTime
379435
v-if="getVersionTime(item.versions[0])"
380436
:datetime="getVersionTime(item.versions[0])!"
@@ -411,6 +467,11 @@ const flatItems = computed<FlatItem[]>(() => {
411467
? 'i-lucide:octagon-alert'
412468
: undefined
413469
"
470+
:title="
471+
fullVersionMap?.get(item.version)?.deprecated
472+
? $t('package.versions.deprecated_title', { version: item.version })
473+
: item.version
474+
"
414475
dir="ltr"
415476
>
416477
{{ item.version }}
@@ -424,6 +485,7 @@ const flatItems = computed<FlatItem[]>(() => {
424485
:key="tag"
425486
class="text-4xs font-semibold uppercase tracking-wide"
426487
:class="tag === 'latest' ? 'text-accent' : 'text-fg-subtle'"
488+
:title="tag"
427489
>
428490
{{ tag }}
429491
</span>
@@ -521,17 +583,4 @@ const flatItems = computed<FlatItem[]>(() => {
521583
opacity: 0;
522584
transform: scale(0.5);
523585
}
524-
525-
@keyframes searching {
526-
from {
527-
transform: rotate(0deg) translateY(-2px) rotate(0deg);
528-
}
529-
to {
530-
transform: rotate(360deg) translateY(-2px) rotate(-360deg);
531-
}
532-
}
533-
534-
.animate-searching {
535-
animation: searching 1.2s linear infinite;
536-
}
537586
</style>

i18n/locales/ar-EG.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,6 @@
161161
},
162162
"page_title": "إصدارات {name} — npmx",
163163
"current_tags": "الوسوم الحالية",
164-
"version_filter_placeholder": "تصفية الإصدارات…",
165-
"version_filter_label": "تصفية الإصدارات",
166164
"no_match_filter": "لا توجد إصدارات مطابقة"
167165
},
168166
"dependencies": {

i18n/locales/cs-CZ.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,8 +441,6 @@
441441
},
442442
"page_title": "Historie verzí",
443443
"current_tags": "Aktuální značky",
444-
"version_filter_placeholder": "Filtrovat verze…",
445-
"version_filter_label": "Filtrovat verze",
446444
"no_match_filter": "Žádné verze neodpovídají {filter}"
447445
},
448446
"dependencies": {

i18n/locales/de.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,8 +440,6 @@
440440
},
441441
"page_title": "Versionshistorie",
442442
"current_tags": "Aktuelle Tags",
443-
"version_filter_placeholder": "Versionen filtern…",
444-
"version_filter_label": "Versionen filtern",
445443
"no_match_filter": "Keine Versionen entsprechen {filter}"
446444
},
447445
"dependencies": {

i18n/locales/en.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,8 +441,6 @@
441441
},
442442
"page_title": "Version History",
443443
"current_tags": "Current Tags",
444-
"version_filter_placeholder": "Filter versions…",
445-
"version_filter_label": "Filter versions",
446444
"no_match_filter": "No versions match {filter}"
447445
},
448446
"dependencies": {

i18n/locales/es.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,8 +441,6 @@
441441
},
442442
"page_title": "Historial de versiones",
443443
"current_tags": "Etiquetas actuales",
444-
"version_filter_placeholder": "Filtrar versiones…",
445-
"version_filter_label": "Filtrar versiones",
446444
"no_match_filter": "Ninguna versión coincide con {filter}"
447445
},
448446
"dependencies": {

i18n/locales/fr-FR.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -437,8 +437,6 @@
437437
"no_matches": "Aucune version ne correspond à cette plage",
438438
"page_title": "Historique des versions",
439439
"current_tags": "Tags actuels",
440-
"version_filter_placeholder": "Filtrer les versions...",
441-
"version_filter_label": "Filtrer les versions",
442440
"no_match_filter": "Aucune version ne correspond à {filter}",
443441
"copy_alt": {
444442
"per_version_analysis": "La version {version} a été téléchargée {downloads} fois",

i18n/locales/hi-IN.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,8 +426,6 @@
426426
},
427427
"page_title": "संस्करण इतिहास",
428428
"current_tags": "वर्तमान टैग्स",
429-
"version_filter_placeholder": "संस्करण फ़िल्टर करें…",
430-
"version_filter_label": "संस्करण फ़िल्टर करें",
431429
"no_match_filter": "कोई संस्करण {filter} से मेल नहीं खाता"
432430
},
433431
"dependencies": {

i18n/locales/id-ID.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,8 +441,6 @@
441441
},
442442
"page_title": "Riwayat Versi",
443443
"current_tags": "Tag Saat Ini",
444-
"version_filter_placeholder": "Filter versi…",
445-
"version_filter_label": "Filter versi",
446444
"no_match_filter": "Tidak ada versi yang cocok dengan {filter}"
447445
},
448446
"dependencies": {

i18n/locales/mr-IN.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,8 +410,6 @@
410410
},
411411
"page_title": "आवृत्ती इतिहास",
412412
"current_tags": "सध्याचे टॅग्स",
413-
"version_filter_placeholder": "आवृत्त्या फिल्टर करा…",
414-
"version_filter_label": "आवृत्त्या फिल्टर करा",
415413
"no_match_filter": "{filter} शी जुळणारी कोणतीही आवृत्ती नाही"
416414
},
417415
"dependencies": {

0 commit comments

Comments
 (0)