Skip to content

Commit 879a7d4

Browse files
committed
feat: support filtering
1 parent bdffaf3 commit 879a7d4

1 file changed

Lines changed: 50 additions & 53 deletions

File tree

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

Lines changed: 50 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
<script setup lang="ts">
22
import { WindowVirtualizer } from 'virtua/vue'
33
import { getVersions } from 'fast-npm-meta'
4+
import { validRange } from 'semver'
45
import {
56
buildVersionToTagsMap,
67
buildTaggedVersionRows,
8+
filterVersions,
79
getVersionGroupKey,
810
getVersionGroupLabel,
911
} from '~/utils/versions'
@@ -17,7 +19,6 @@ definePageMeta({
1719
const SSR_COUNT = 20
1820
1921
const route = useRoute()
20-
const router = useRouter()
2122
2223
const packageName = computed(() => {
2324
const { org, name } = route.params as { org?: string; name: string }
@@ -125,6 +126,33 @@ async function toggleGroup(groupKey: string) {
125126
}
126127
}
127128
129+
// ─── Version filter ───────────────────────────────────────────────────────────
130+
131+
const versionFilter = ref('')
132+
const isFilterActive = computed(() => versionFilter.value.trim() !== '')
133+
134+
const filteredVersionSet = computed(() => {
135+
const trimmed = versionFilter.value.trim()
136+
if (!trimmed) return null
137+
// Try semver range first (e.g. "^2.0.0", ">=1 <3")
138+
if (validRange(trimmed)) {
139+
return filterVersions(versionStrings.value, trimmed)
140+
}
141+
// Fallback: substring match (e.g. "2.4", "beta")
142+
const lower = trimmed.toLowerCase()
143+
return new Set(versionStrings.value.filter(v => v.toLowerCase().includes(lower)))
144+
})
145+
146+
const filteredGroups = computed(() => {
147+
if (!isFilterActive.value || !filteredVersionSet.value) return versionGroups.value
148+
return versionGroups.value
149+
.map(group => ({
150+
...group,
151+
versions: group.versions.filter(v => filteredVersionSet.value!.has(v)),
152+
}))
153+
.filter(group => group.versions.length > 0)
154+
})
155+
128156
// ─── Flat list for virtual rendering ──────────────────────────────────────────
129157
130158
type FlatItem =
@@ -133,14 +161,14 @@ type FlatItem =
133161
134162
const flatItems = computed<FlatItem[]>(() => {
135163
const items: FlatItem[] = []
136-
for (const group of versionGroups.value) {
164+
for (const group of filteredGroups.value) {
137165
items.push({
138166
type: 'header',
139167
groupKey: group.groupKey,
140168
label: group.label,
141169
versions: group.versions,
142170
})
143-
if (expandedGroups.value.has(group.groupKey)) {
171+
if (expandedGroups.value.has(group.groupKey) || isFilterActive.value) {
144172
for (const version of group.versions) {
145173
items.push({ type: 'version', version, groupKey: group.groupKey })
146174
}
@@ -161,26 +189,6 @@ const selectedChangelogContent = computed(() => {
161189
// function toggleChangelog(version: string) {
162190
// selectedChangelogVersion.value = selectedChangelogVersion.value === version ? null : version
163191
// }
164-
165-
// ─── Jump to version ──────────────────────────────────────────────────────────
166-
167-
const jumpVersion = ref('')
168-
const jumpError = ref('')
169-
170-
function navigateToVersion() {
171-
const v = jumpVersion.value.trim()
172-
if (!v) return
173-
if (!versionStrings.value.includes(v)) {
174-
jumpError.value = `"${v}" not found`
175-
return
176-
}
177-
jumpError.value = ''
178-
router.push(packageRoute(packageName.value, v))
179-
}
180-
181-
watch(jumpVersion, () => {
182-
jumpError.value = ''
183-
})
184192
</script>
185193

186194
<template>
@@ -201,35 +209,14 @@ watch(jumpVersion, () => {
201209
<span class="text-fg-subtle shrink-0">/</span>
202210
<span class="font-mono text-sm text-fg-muted shrink-0">Version History</span>
203211
</div>
204-
<div class="flex flex-col items-end gap-1 shrink-0">
205-
<div class="flex items-center gap-2">
206-
<InputBase
207-
v-model="jumpVersion"
208-
type="text"
209-
placeholder="Jump to version…"
210-
aria-label="Jump to version"
211-
size="small"
212-
class="w-36 sm:w-44 font-mono"
213-
@keydown.enter="navigateToVersion"
214-
/>
215-
<ButtonBase
216-
variant="secondary"
217-
size="small"
218-
classicon="i-lucide:arrow-right"
219-
:disabled="!jumpVersion.trim()"
220-
@click="navigateToVersion"
221-
>
222-
Go
223-
</ButtonBase>
224-
</div>
225-
<p
226-
v-if="jumpError"
227-
role="alert"
228-
class="text-red-500 dark:text-red-400 text-xs leading-none"
229-
>
230-
{{ jumpError }}
231-
</p>
232-
</div>
212+
<InputBase
213+
v-model="versionFilter"
214+
type="text"
215+
placeholder="Filter versions…"
216+
aria-label="Filter versions"
217+
size="small"
218+
class="w-36 sm:w-44 font-mono"
219+
/>
233220
</div>
234221
</header>
235222

@@ -345,8 +332,18 @@ watch(jumpVersion, () => {
345332
</span>
346333
</h2>
347334

335+
<!-- No filter matches -->
336+
<div
337+
v-if="isFilterActive && filteredGroups.length === 0"
338+
class="px-1 py-4 text-sm text-fg-subtle"
339+
role="status"
340+
aria-live="polite"
341+
>
342+
No versions match <span class="font-mono">{{ versionFilter }}</span>
343+
</div>
344+
348345
<!-- List + changelog side panel -->
349-
<div class="flex">
346+
<div v-else class="flex">
350347
<!-- Version list (grouped by major, virtualized) -->
351348
<div
352349
class="flex-1 min-w-0 border-y sm:border border-border sm:rounded-lg sm:overflow-hidden"

0 commit comments

Comments
 (0)