Skip to content

Commit e044f09

Browse files
committed
Merge branch 'ux/make-package-name-selectable' of https://github.com/Adebesin-Cell/npmx.dev into ux/make-package-name-selectable
2 parents 6308420 + 0a3d6c2 commit e044f09

18 files changed

Lines changed: 142 additions & 187 deletions

app/components/CollapsibleSection.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ useHead({
123123
:id="contentId"
124124
class="grid ms-6 transition-[grid-template-rows] duration-200 ease-in-out collapsible-content overflow-hidden"
125125
>
126-
<div class="min-h-0">
126+
<div class="min-h-0 min-w-0">
127127
<slot />
128128
</div>
129129
</div>

app/components/MarkdownText.vue

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<script setup lang="ts">
2+
import { decodeHtmlEntities } from '~/utils/formatters'
3+
24
const props = defineProps<{
35
text: string
46
/** When true, renders link text without the anchor tag (useful when inside another link) */
@@ -21,8 +23,11 @@ function stripMarkdownImages(text: string): string {
2123
2224
// Strip HTML tags and escape remaining HTML to prevent XSS
2325
function stripAndEscapeHtml(text: string): string {
24-
// First strip markdown image badges
25-
let stripped = stripMarkdownImages(text)
26+
// First decode any HTML entities in the input
27+
let stripped = decodeHtmlEntities(text)
28+
29+
// Then strip markdown image badges
30+
stripped = stripMarkdownImages(stripped)
2631
2732
// Then strip actual HTML tags (keep their text content)
2833
// Only match tags that start with a letter or / (to avoid matching things like "a < b > c")

app/components/OgImage/Package.vue

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
<script setup lang="ts">
2-
withDefaults(
2+
import { computed, toRefs } from 'vue'
3+
4+
const props = withDefaults(
35
defineProps<{
46
name: string
57
version: string
8+
stars: number
69
downloads?: string
710
license?: string
811
primaryColor?: string
@@ -13,6 +16,15 @@ withDefaults(
1316
primaryColor: '#60a5fa',
1417
},
1518
)
19+
20+
const { name, version, stars, downloads, license, primaryColor } = toRefs(props)
21+
22+
const formattedStars = computed(() =>
23+
Intl.NumberFormat('en', {
24+
notation: 'compact',
25+
maximumFractionDigits: 1,
26+
}).format(stars.value),
27+
)
1628
</script>
1729

1830
<template>
@@ -88,6 +100,18 @@ withDefaults(
88100
</span>
89101
</span>
90102
<span v-if="license"> • {{ license }}</span>
103+
<span class="flex items-center gap-2">
104+
<span>•</span>
105+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32px" height="32px">
106+
<path
107+
fill="currentColor"
108+
d="m16 6.52l2.76 5.58l.46 1l1 .15l6.16.89l-4.38 4.3l-.75.73l.18 1l1.05 6.13l-5.51-2.89L16 23l-.93.49l-5.51 2.85l1-6.13l.18-1l-.74-.77l-4.42-4.35l6.16-.89l1-.15l.46-1zM16 2l-4.55 9.22l-10.17 1.47l7.36 7.18L6.9 30l9.1-4.78L25.1 30l-1.74-10.13l7.36-7.17l-10.17-1.48Z"
109+
/>
110+
</svg>
111+
<span>
112+
{{ formattedStars }}
113+
</span>
114+
</span>
91115
</div>
92116
</div>
93117

app/components/PackageCard.vue

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ const props = defineProps<{
77
/** Whether to show the publisher username */
88
showPublisher?: boolean
99
prefetch?: boolean
10-
selected?: boolean
1110
index?: number
1211
/** Search query for highlighting exact matches */
1312
searchQuery?: string
@@ -20,17 +19,12 @@ const isExactMatch = computed(() => {
2019
const name = props.result.package.name.toLowerCase()
2120
return query === name
2221
})
23-
24-
const emit = defineEmits<{
25-
focus: [index: number]
26-
}>()
2722
</script>
2823

2924
<template>
3025
<article
31-
class="group card-interactive scroll-mt-48 scroll-mb-6 relative focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-bg focus-within:ring-offset-2 focus-within:ring-fg/50"
26+
class="group card-interactive scroll-mt-48 scroll-mb-6 relative focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-bg focus-within:ring-offset-2 focus-within:ring-fg/50 focus-within:bg-bg-muted focus-within:border-border-hover"
3227
:class="{
33-
'bg-bg-muted border-border-hover': selected,
3428
'border-accent/30 bg-accent/5': isExactMatch,
3529
}"
3630
>
@@ -50,8 +44,6 @@ const emit = defineEmits<{
5044
:prefetch-on="prefetch ? 'visibility' : 'interaction'"
5145
class="decoration-none scroll-mt-48 scroll-mb-6 after:content-[''] after:absolute after:inset-0"
5246
:data-result-index="index"
53-
@focus="index != null && emit('focus', index)"
54-
@mouseenter="index != null && emit('focus', index)"
5547
>{{ result.package.name }}</NuxtLink
5648
>
5749
<span

app/components/PackageList.vue

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ const props = defineProps<{
2929
pageSize?: PageSize
3030
/** Initial page to scroll to (1-indexed) */
3131
initialPage?: number
32-
/** Selected result index (for keyboard navigation) */
33-
selectedIndex?: number
3432
/** Search query for highlighting exact matches */
3533
searchQuery?: string
3634
/** View mode: cards or table */
@@ -48,8 +46,6 @@ const emit = defineEmits<{
4846
'loadMore': []
4947
/** Emitted when the visible page changes */
5048
'pageChange': [page: number]
51-
/** Emitted when a result is hovered/focused */
52-
'select': [index: number]
5349
/** Emitted when sort option changes (table view) */
5450
'update:sortOption': [option: SortOption]
5551
/** Emitted when a keyword is clicked */
@@ -153,9 +149,7 @@ defineExpose({
153149
:results="displayedResults"
154150
:columns="columns"
155151
v-model:sort-option="sortOption"
156-
:selected-index="selectedIndex"
157152
:is-loading="isLoading"
158-
@select="emit('select', $event)"
159153
@click-keyword="emit('clickKeyword', $event)"
160154
/>
161155
</template>
@@ -179,12 +173,10 @@ defineExpose({
179173
:result="item as NpmSearchResult"
180174
:heading-level="headingLevel"
181175
:show-publisher="showPublisher"
182-
:selected="index === (selectedIndex ?? -1)"
183176
:index="index"
184177
:search-query="searchQuery"
185178
class="motion-safe:animate-fade-in motion-safe:animate-fill-both"
186179
:style="{ animationDelay: `${Math.min(index * 0.02, 0.3)}s` }"
187-
@focus="emit('select', $event)"
188180
/>
189181
</div>
190182
</template>
@@ -199,7 +191,6 @@ defineExpose({
199191
:result="item"
200192
:heading-level="headingLevel"
201193
:show-publisher="showPublisher"
202-
:selected="index === (selectedIndex ?? -1)"
203194
:index="index"
204195
:search-query="searchQuery"
205196
/>
@@ -230,12 +221,10 @@ defineExpose({
230221
:result="item"
231222
:heading-level="headingLevel"
232223
:show-publisher="showPublisher"
233-
:selected="index === (selectedIndex ?? -1)"
234224
:index="index"
235225
:search-query="searchQuery"
236226
class="motion-safe:animate-fade-in motion-safe:animate-fill-both"
237227
:style="{ animationDelay: `${Math.min(index * 0.02, 0.3)}s` }"
238-
@focus="emit('select', $event)"
239228
/>
240229
</li>
241230
</ol>

app/components/PackagePlaygrounds.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
22
import type { PlaygroundLink } from '#shared/types'
3+
import { decodeHtmlEntities } from '~/utils/formatters'
34
45
const props = defineProps<{
56
links: PlaygroundLink[]
@@ -126,7 +127,7 @@ function focusMenuItem(index: number) {
126127
:class="[getIcon(firstLink.provider), getColor(firstLink.provider), 'w-4 h-4 shrink-0']"
127128
aria-hidden="true"
128129
/>
129-
<span class="truncate text-fg-muted">{{ firstLink.label }}</span>
130+
<span class="truncate text-fg-muted">{{ decodeHtmlEntities(firstLink.label) }}</span>
130131
</a>
131132
</AppTooltip>
132133

@@ -182,7 +183,7 @@ function focusMenuItem(index: number) {
182183
:class="[getIcon(link.provider), getColor(link.provider), 'w-4 h-4 shrink-0']"
183184
aria-hidden="true"
184185
/>
185-
<span class="truncate">{{ link.label }}</span>
186+
<span class="truncate">{{ decodeHtmlEntities(link.label) }}</span>
186187
</a>
187188
</AppTooltip>
188189
</div>

app/components/PackageTable.vue

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@ import { buildSortOption, parseSortOption, toggleDirection } from '#shared/types
66
const props = defineProps<{
77
results: NpmSearchResult[]
88
columns: ColumnConfig[]
9-
selectedIndex?: number
109
isLoading?: boolean
1110
}>()
1211
1312
const sortOption = defineModel<SortOption>('sortOption')
1413
1514
const emit = defineEmits<{
16-
select: [index: number]
1715
clickKeyword: [keyword: string]
1816
}>()
1917
@@ -318,9 +316,7 @@ function getColumnLabelKey(id: ColumnId): string {
318316
:key="result.package.name"
319317
:result="result"
320318
:columns="columns"
321-
:selected="selectedIndex === index"
322319
:index="index"
323-
@focus="emit('select', index)"
324320
@click-keyword="emit('clickKeyword', $event)"
325321
/>
326322
</template>

app/components/PackageTableRow.vue

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@ import type { ColumnConfig } from '#shared/types/preferences'
55
const props = defineProps<{
66
result: NpmSearchResult
77
columns: ColumnConfig[]
8-
selected?: boolean
98
index?: number
109
}>()
1110
1211
const emit = defineEmits<{
13-
focus: []
1412
clickKeyword: [keyword: string]
1513
}>()
1614
@@ -45,10 +43,9 @@ const allMaintainersText = computed(() => {
4543

4644
<template>
4745
<tr
48-
class="border-b border-border hover:bg-bg-muted transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-inset focus-visible:outline-none"
49-
:class="{ 'bg-bg-muted': selected }"
46+
class="border-b border-border hover:bg-bg-muted transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-inset focus-visible:outline-none focus:bg-bg-muted"
5047
tabindex="0"
51-
@focus="emit('focus')"
48+
:data-result-index="index"
5249
>
5350
<!-- Name (always visible) -->
5451
<td class="py-2 px-3">

app/components/PackageVersions.vue

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -356,11 +356,11 @@ function getTagVersions(tag: string): VersionDisplay[] {
356356

357357
<!-- Version info -->
358358
<div class="flex-1 py-1.5 min-w-0 flex gap-2 justify-between items-center">
359-
<div>
359+
<div class="overflow-hidden">
360360
<div>
361361
<NuxtLink
362362
:to="versionRoute(row.primaryVersion.version)"
363-
class="font-mono text-sm transition-colors duration-200 truncate inline-flex items-center gap-1"
363+
class="block font-mono text-sm transition-colors duration-200 truncate inline-flex items-center gap-1"
364364
:class="
365365
row.primaryVersion.deprecated
366366
? 'text-red-400 hover:text-red-300'
@@ -421,7 +421,7 @@ function getTagVersions(tag: string): VersionDisplay[] {
421421
<div class="flex items-center justify-between gap-2">
422422
<NuxtLink
423423
:to="versionRoute(v.version)"
424-
class="font-mono text-xs transition-colors duration-200 truncate inline-flex items-center gap-1"
424+
class="block font-mono text-xs transition-colors duration-200 truncate inline-flex items-center gap-1"
425425
:class="
426426
v.deprecated
427427
? 'text-red-400 hover:text-red-300'
@@ -524,7 +524,7 @@ function getTagVersions(tag: string): VersionDisplay[] {
524524
<div class="flex items-center justify-between gap-2">
525525
<NuxtLink
526526
:to="versionRoute(row.primaryVersion.version)"
527-
class="font-mono text-xs transition-colors duration-200 truncate inline-flex items-center gap-1"
527+
class="block font-mono text-xs transition-colors duration-200 truncate inline-flex items-center gap-1"
528528
:class="
529529
row.primaryVersion.deprecated
530530
? 'text-red-400 hover:text-red-300'
@@ -600,7 +600,7 @@ function getTagVersions(tag: string): VersionDisplay[] {
600600
<NuxtLink
601601
v-if="group.versions[0]?.version"
602602
:to="versionRoute(group.versions[0]?.version)"
603-
class="font-mono text-xs transition-colors duration-200 truncate inline-flex items-center gap-1"
603+
class="block font-mono text-xs transition-colors duration-200 truncate inline-flex items-center gap-1"
604604
:class="
605605
group.versions[0]?.deprecated
606606
? 'text-red-400 hover:text-red-300'
@@ -661,7 +661,7 @@ function getTagVersions(tag: string): VersionDisplay[] {
661661
<NuxtLink
662662
v-if="group.versions[0]?.version"
663663
:to="versionRoute(group.versions[0]?.version)"
664-
class="font-mono text-xs transition-colors duration-200 truncate inline-flex items-center gap-1"
664+
class="block font-mono text-xs transition-colors duration-200 truncate inline-flex items-center gap-1"
665665
:class="
666666
group.versions[0]?.deprecated
667667
? 'text-red-400 hover:text-red-300'
@@ -683,7 +683,7 @@ function getTagVersions(tag: string): VersionDisplay[] {
683683
{{ group.versions[0]?.version }}
684684
</NuxtLink>
685685
</div>
686-
<div class="flex items-center gap-2 shrink-0">
686+
<div class="flex items-center gap-2 shrink-0 pe-2">
687687
<DateTime
688688
v-if="group.versions[0]?.time"
689689
:datetime="group.versions[0]?.time"
@@ -720,7 +720,7 @@ function getTagVersions(tag: string): VersionDisplay[] {
720720
<div class="flex items-center justify-between gap-2">
721721
<NuxtLink
722722
:to="versionRoute(v.version)"
723-
class="font-mono text-xs transition-colors duration-200 truncate inline-flex items-center gap-1"
723+
class="block font-mono text-xs transition-colors duration-200 truncate inline-flex items-center gap-1"
724724
:class="
725725
v.deprecated
726726
? 'text-red-400 hover:text-red-300'
@@ -739,7 +739,7 @@ function getTagVersions(tag: string): VersionDisplay[] {
739739
/>
740740
{{ v.version }}
741741
</NuxtLink>
742-
<div class="flex items-center gap-2 shrink-0">
742+
<div class="flex items-center gap-2 shrink-0 pe-2">
743743
<DateTime
744744
v-if="v.time"
745745
:datetime="v.time"

app/components/SearchSuggestionCard.vue

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,17 @@ defineProps<{
44
type: 'user' | 'org'
55
/** The name (username or org name) */
66
name: string
7-
/** Whether this suggestion is currently selected (keyboard nav) */
8-
selected?: boolean
97
/** Whether this is an exact match for the query */
108
isExactMatch?: boolean
119
/** Index for keyboard navigation */
1210
index?: number
1311
}>()
14-
15-
const emit = defineEmits<{
16-
focus: [index: number]
17-
}>()
1812
</script>
1913

2014
<template>
2115
<article
22-
class="group card-interactive relative focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-bg focus-within:ring-offset-2 focus-within:ring-fg/50"
16+
class="group card-interactive relative focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-bg focus-within:ring-offset-2 focus-within:ring-fg/50 focus-within:bg-bg-muted focus-within:border-border-hover"
2317
:class="{
24-
'bg-bg-muted border-border-hover': selected,
2518
'border-accent/30 bg-accent/5': isExactMatch,
2619
}"
2720
>
@@ -35,8 +28,6 @@ const emit = defineEmits<{
3528
:to="type === 'user' ? `/~${name}` : `/@${name}`"
3629
:data-suggestion-index="index"
3730
class="flex items-center gap-4 focus-visible:outline-none after:content-[''] after:absolute after:inset-0"
38-
@focus="index != null && emit('focus', index)"
39-
@mouseenter="index != null && emit('focus', index)"
4031
>
4132
<!-- Avatar placeholder -->
4233
<div

0 commit comments

Comments
 (0)