Skip to content

Commit b45acab

Browse files
committed
Merge remote-tracking branch 'origin/main' into chore/ignore-internal-http-message
2 parents 60aecfe + d189f99 commit b45acab

44 files changed

Lines changed: 1211 additions & 549 deletions

Some content is hidden

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

.github/workflows/provenance.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ permissions:
1414
contents: read
1515
jobs:
1616
check-provenance:
17-
runs-on: ubuntu-latest
17+
runs-on: ubuntu-slim
1818
steps:
1919
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
2020
with:

.github/workflows/semantic-pull-requests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
pull-requests: read # for amannn/action-semantic-pull-request to analyze PRs
1717
statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR
1818
if: github.repository == 'npmx-dev/npmx.dev'
19-
runs-on: ubuntu-latest
19+
runs-on: ubuntu-slim
2020
name: semantic-pr
2121
steps:
2222
- name: Validate PR title

app/components/AppHeader.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const showMobileMenu = shallowRef(false)
1919
const route = useRoute()
2020
const isMobile = useIsMobile()
2121
const isSearchExpandedManually = shallowRef(false)
22-
const searchBoxRef = shallowRef<{ focus: () => void } | null>(null)
22+
const searchBoxRef = useTemplateRef('searchBoxRef')
2323
2424
// On search page, always show search expanded on mobile
2525
const isOnHomePage = computed(() => route.name === 'index')

app/components/CallToAction.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ const socialLinks = {
1212
{{ $t('about.get_involved.title') }}
1313
</h2>
1414

15-
<div class="grid gap-4 sm:grid-cols-3">
15+
<div class="grid gap-4 sm:grid-cols-3 sm:items-stretch sm:grid-rows-[auto,1fr,auto]">
1616
<a
1717
:href="socialLinks.github"
1818
target="_blank"
1919
rel="noopener noreferrer"
20-
class="group flex flex-col gap-3 p-4 rounded-lg bg-bg-subtle hover:bg-bg-elevated border border-border hover:border-border-hover transition-all duration-200"
20+
class="group grid gap-3 p-4 rounded-lg bg-bg-subtle hover:bg-bg-elevated border border-border hover:border-border-hover transition-all duration-200 sm:grid-rows-subgrid sm:row-span-3"
2121
>
2222
<div class="flex gap-2">
2323
<span class="i-carbon:logo-github shrink-0 mt-1 w-5 h-5 text-fg" aria-hidden="true" />
@@ -40,7 +40,7 @@ const socialLinks = {
4040
:href="socialLinks.discord"
4141
target="_blank"
4242
rel="noopener noreferrer"
43-
class="group flex flex-col gap-3 p-4 rounded-lg bg-bg-subtle hover:bg-bg-elevated border border-border hover:border-border-hover transition-all duration-200"
43+
class="group grid gap-3 p-4 rounded-lg bg-bg-subtle hover:bg-bg-elevated border border-border hover:border-border-hover transition-all duration-200 sm:grid-rows-subgrid sm:row-span-3"
4444
>
4545
<div class="flex gap-2">
4646
<span class="i-carbon:chat shrink-0 mt-1 w-5 h-5 text-fg" aria-hidden="true" />
@@ -63,7 +63,7 @@ const socialLinks = {
6363
:href="socialLinks.bluesky"
6464
target="_blank"
6565
rel="noopener noreferrer"
66-
class="group flex flex-col gap-3 p-4 rounded-lg bg-bg-subtle hover:bg-bg-elevated border border-border hover:border-border-hover transition-all duration-200"
66+
class="group grid gap-3 p-4 rounded-lg bg-bg-subtle hover:bg-bg-elevated border border-border hover:border-border-hover transition-all duration-200 sm:grid-rows-subgrid sm:row-span-3"
6767
>
6868
<div class="flex gap-2">
6969
<span class="i-simple-icons:bluesky shrink-0 mt-1 w-5 h-5 text-fg" aria-hidden="true" />

app/components/Filter/Panel.vue

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import type {
88
} from '#shared/types/preferences'
99
import {
1010
DOWNLOAD_RANGES,
11-
SEARCH_SCOPE_OPTIONS,
12-
SECURITY_FILTER_OPTIONS,
11+
SEARCH_SCOPE_VALUES,
12+
SECURITY_FILTER_VALUES,
1313
UPDATED_WITHIN_OPTIONS,
1414
} from '#shared/types/preferences'
1515
@@ -205,20 +205,20 @@ const hasActiveFilters = computed(() => !!filterSummary.value)
205205
:aria-label="$t('filters.search_scope')"
206206
>
207207
<button
208-
v-for="option in SEARCH_SCOPE_OPTIONS"
209-
:key="option.value"
208+
v-for="scope in SEARCH_SCOPE_VALUES"
209+
:key="scope"
210210
type="button"
211211
class="px-2 py-0.5 text-xs font-mono rounded-sm transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-1"
212212
:class="
213-
filters.searchScope === option.value
213+
filters.searchScope === scope
214214
? 'bg-bg-muted text-fg'
215215
: 'text-fg-muted hover:text-fg'
216216
"
217-
:aria-pressed="filters.searchScope === option.value"
218-
:title="$t(getScopeDescriptionKey(option.value))"
219-
@click="emit('update:searchScope', option.value)"
217+
:aria-pressed="filters.searchScope === scope"
218+
:title="$t(getScopeDescriptionKey(scope))"
219+
@click="emit('update:searchScope', scope)"
220220
>
221-
{{ $t(getScopeLabelKey(option.value)) }}
221+
{{ $t(getScopeLabelKey(scope)) }}
222222
</button>
223223
</div>
224224
</div>
@@ -301,18 +301,18 @@ const hasActiveFilters = computed(() => !!filterSummary.value)
301301
</legend>
302302
<div class="flex flex-wrap gap-2" role="radiogroup" :aria-label="$t('filters.security')">
303303
<button
304-
v-for="option in SECURITY_FILTER_OPTIONS"
305-
:key="option.value"
304+
v-for="security in SECURITY_FILTER_VALUES"
305+
:key="security"
306306
type="button"
307307
role="radio"
308308
disabled
309-
:aria-checked="filters.security === option.value"
309+
:aria-checked="filters.security === security"
310310
class="tag transition-colors duration-200 opacity-50 cursor-not-allowed focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-1"
311311
:class="
312-
filters.security === option.value ? 'bg-fg text-bg border-fg hover:text-bg/70' : ''
312+
filters.security === security ? 'bg-fg text-bg border-fg hover:text-bg/70' : ''
313313
"
314314
>
315-
{{ $t(getSecurityLabelKey(option.value)) }}
315+
{{ $t(getSecurityLabelKey(security)) }}
316316
</button>
317317
</div>
318318
</fieldset>

app/components/Header/SearchBox.vue

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,29 @@ function handleSearchFocus() {
7979
emit('focus')
8080
}
8181
82+
function handleSubmit() {
83+
if (pagesWithLocalFilter.has(route.name as string)) {
84+
router.push({
85+
name: 'search',
86+
query: {
87+
q: searchQuery.value,
88+
},
89+
})
90+
} else {
91+
updateUrlQuery.flush()
92+
}
93+
}
94+
8295
// Expose focus method for parent components
83-
const inputRef = shallowRef<HTMLInputElement | null>(null)
96+
const inputRef = useTemplateRef('inputRef')
8497
function focus() {
8598
inputRef.value?.focus()
8699
}
87100
defineExpose({ focus })
88101
</script>
89102
<template>
90103
<search v-if="showSearchBar" :class="'flex-1 sm:max-w-md ' + inputClass">
91-
<form method="GET" action="/search" class="relative">
104+
<form method="GET" action="/search" class="relative" @submit.prevent="handleSubmit">
92105
<label for="header-search" class="sr-only">
93106
{{ $t('search.label') }}
94107
</label>

app/components/Modal.client.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const props = defineProps<{
33
modalTitle: string
44
}>()
55
6-
const dialogRef = ref<HTMLDialogElement>()
6+
const dialogRef = useTemplateRef('dialogRef')
77
88
const modalTitleId = computed(() => {
99
const id = getCurrentInstance()?.attrs.id

app/components/Package/Card.vue

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<script setup lang="ts">
2+
import type { StructuredFilters } from '#shared/types/preferences'
3+
24
const props = defineProps<{
35
/** The search result object containing package data */
46
result: NpmSearchResult
@@ -8,10 +10,16 @@ const props = defineProps<{
810
showPublisher?: boolean
911
prefetch?: boolean
1012
index?: number
13+
/** Filters to apply to the results */
14+
filters?: StructuredFilters
1115
/** Search query for highlighting exact matches */
1216
searchQuery?: string
1317
}>()
1418
19+
const emit = defineEmits<{
20+
clickKeyword: [keyword: string]
21+
}>()
22+
1523
/** Check if this package is an exact match for the search query */
1624
const isExactMatch = computed(() => {
1725
if (!props.searchQuery) return false
@@ -149,14 +157,29 @@ const pkgDescription = useMarkdown(() => ({
149157
</div>
150158
</div>
151159

152-
<ul
160+
<div
153161
v-if="result.package.keywords?.length"
154162
:aria-label="$t('package.card.keywords')"
155-
class="relative z-10 flex flex-wrap gap-1.5 mt-3 pt-3 border-t border-border list-none m-0 p-0"
163+
class="relative z-10 flex flex-wrap gap-1.5 mt-3 pt-3 border-t border-border list-none m-0 p-0 pointer-events-none"
156164
>
157-
<li v-for="keyword in result.package.keywords.slice(0, 5)" :key="keyword" class="tag">
165+
<button
166+
v-for="keyword in result.package.keywords.slice(0, 5)"
167+
:key="keyword"
168+
type="button"
169+
class="tag text-xs hover:bg-fg hover:text-bg hover:border-fg transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-offset-1 border-solid pointer-events-auto"
170+
:class="{ 'bg-fg text-bg hover:opacity-80': props.filters?.keywords.includes(keyword) }"
171+
:title="`Filter by ${keyword}`"
172+
@click.stop="emit('clickKeyword', keyword)"
173+
>
158174
{{ keyword }}
159-
</li>
160-
</ul>
175+
</button>
176+
<span
177+
v-if="result.package.keywords.length > 5"
178+
class="tag text-fg-subtle text-xs border-none bg-transparent pointer-events-auto"
179+
:title="result.package.keywords.slice(5).join(', ')"
180+
>
181+
+{{ result.package.keywords.length - 5 }}
182+
</span>
183+
</div>
161184
</BaseCard>
162185
</template>

app/components/Package/ClaimPackageModal.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ async function handleClaim() {
9696
}
9797
}
9898
99-
const dialogRef = ref<HTMLDialogElement>()
99+
const dialogRef = useTemplateRef('dialogRef')
100100
101101
function open() {
102102
// Reset state and check availability each time modal is opened

app/components/Package/List.vue

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ const SSR_COUNT = 20
1717
const props = defineProps<{
1818
/** List of search results to display */
1919
results: NpmSearchResult[]
20+
/** Filters to apply to the results */
21+
filters?: StructuredFilters
2022
/** Heading level for package names */
2123
headingLevel?: 'h2' | 'h3'
2224
/** Whether to show publisher username on cards */
@@ -39,6 +41,8 @@ const props = defineProps<{
3941
paginationMode?: PaginationMode
4042
/** Current page (1-indexed) for paginated mode */
4143
currentPage?: number
44+
/** When true, shows search-specific UI (relevance sort, no filters) */
45+
searchContext?: boolean
4246
}>()
4347
4448
const emit = defineEmits<{
@@ -60,7 +64,11 @@ const sortOption = defineModel<SortOption>('sortOption')
6064
6165
// View mode and columns
6266
const viewMode = computed(() => props.viewMode ?? 'cards')
63-
const columns = computed(() => props.columns ?? DEFAULT_COLUMNS)
67+
const columns = computed(() => {
68+
const targetColumns = props.columns ?? DEFAULT_COLUMNS
69+
if (props.searchContext) return targetColumns.map(column => ({ ...column, sortable: false }))
70+
return targetColumns
71+
})
6472
// Table view forces pagination mode (no virtualization for tables)
6573
const paginationMode = computed(() =>
6674
viewMode.value === 'table' ? 'paginated' : (props.paginationMode ?? 'infinite'),
@@ -147,6 +155,7 @@ defineExpose({
147155
<template v-if="viewMode === 'table'">
148156
<PackageTable
149157
:results="displayedResults"
158+
:filters="filters"
150159
:columns="columns"
151160
v-model:sort-option="sortOption"
152161
:is-loading="isLoading"
@@ -176,7 +185,9 @@ defineExpose({
176185
:index="index"
177186
:search-query="searchQuery"
178187
class="motion-safe:animate-fade-in motion-safe:animate-fill-both"
188+
:filters="filters"
179189
:style="{ animationDelay: `${Math.min(index * 0.02, 0.3)}s` }"
190+
@click-keyword="emit('clickKeyword', $event)"
180191
/>
181192
</div>
182193
</template>
@@ -193,6 +204,8 @@ defineExpose({
193204
:show-publisher="showPublisher"
194205
:index="index"
195206
:search-query="searchQuery"
207+
:filters="filters"
208+
@click-keyword="emit('clickKeyword', $event)"
196209
/>
197210
</div>
198211
</li>
@@ -225,6 +238,8 @@ defineExpose({
225238
:search-query="searchQuery"
226239
class="motion-safe:animate-fade-in motion-safe:animate-fill-both"
227240
:style="{ animationDelay: `${Math.min(index * 0.02, 0.3)}s` }"
241+
:filters="filters"
242+
@click-keyword="emit('clickKeyword', $event)"
228243
/>
229244
</li>
230245
</ol>

0 commit comments

Comments
 (0)