Skip to content

Commit d364269

Browse files
committed
Merge branch 'main' into refactor-remove-class-shortcuts
2 parents 740fbc7 + 368e2d0 commit d364269

28 files changed

Lines changed: 542 additions & 436 deletions

.vscode/extensions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"recommendations": ["oxc.oxc-vscode", "Vue.volar", "lokalise.i18n-ally"]
2+
"recommendations": ["oxc.oxc-vscode", "Vue.volar", "lokalise.i18n-ally", "antfu.unocss"]
33
}

app/app.vue

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup lang="ts">
22
import type { Directions } from '@nuxtjs/i18n'
33
import { useEventListener } from '@vueuse/core'
4+
import { isEditableElement } from '~/utils/input'
45
56
const route = useRoute()
67
const router = useRouter()
@@ -39,12 +40,7 @@ if (import.meta.server) {
3940
// "/" focuses search or navigates to search page
4041
// "?" highlights all keyboard shortcut elements
4142
function handleGlobalKeydown(e: KeyboardEvent) {
42-
const target = e.target as HTMLElement
43-
44-
const isEditableTarget =
45-
target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable
46-
47-
if (isEditableTarget) return
43+
if (isEditableElement(e.target)) return
4844
4945
if (e.key === '/') {
5046
e.preventDefault()

app/components/AppHeader.vue

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<script setup lang="ts">
2+
import { isEditableElement } from '~/utils/input'
3+
24
withDefaults(
35
defineProps<{
46
showLogo?: boolean
@@ -62,11 +64,7 @@ function handleSearchFocus() {
6264
onKeyStroke(
6365
',',
6466
e => {
65-
// Don't trigger if user is typing in an input
66-
const target = e.target as HTMLElement
67-
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
68-
return
69-
}
67+
if (isEditableElement(e.target)) return
7068
7169
e.preventDefault()
7270
navigateTo('/settings')
@@ -79,12 +77,7 @@ onKeyStroke(
7977
e => {
8078
// Allow more specific handlers to take precedence
8179
if (e.defaultPrevented) return
82-
83-
// Don't trigger if user is typing in an input
84-
const target = e.target as HTMLElement
85-
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
86-
return
87-
}
80+
if (isEditableElement(e.target)) return
8881
8982
e.preventDefault()
9083
navigateTo('/compare')

app/components/Card.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ defineProps<{
99

1010
<template>
1111
<article
12-
class="group bg-bg-subtle border border-border rounded-lg p-4 sm:p-6 transition-[border-color,background-color] duration-200 hover:(border-border-hover bg-bg-muted) cursor-pointer relative focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-bg focus-within:ring-offset-2 focus-within:ring-fg/50"
12+
class="group bg-bg-subtle border border-border rounded-lg p-4 sm:p-6 transition-[border-color,background-color] duration-200 hover:(border-border-hover bg-bg-muted) cursor-pointer 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"
1313
:class="{
1414
'bg-bg-muted border-border-hover': selected,
1515
'border-accent/30 bg-accent/5': isExactMatch,

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: 0 additions & 7 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,10 +19,6 @@ 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>
@@ -38,8 +33,6 @@ const emit = defineEmits<{
3833
:prefetch-on="prefetch ? 'visibility' : 'interaction'"
3934
class="decoration-none scroll-mt-48 scroll-mb-6 after:content-[''] after:absolute after:inset-0"
4035
:data-result-index="index"
41-
@focus="index != null && emit('focus', index)"
42-
@mouseenter="index != null && emit('focus', index)"
4336
>{{ result.package.name }}</NuxtLink
4437
>
4538
<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/PackageMetricsBadges.vue

Lines changed: 49 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { NuxtLink } from '#components'
33
44
const props = defineProps<{
55
packageName: string
6+
isBinary?: boolean
67
version?: string
78
}>()
89
@@ -43,7 +44,7 @@ const typesTooltip = computed(() => {
4344
4445
const typesHref = computed(() => {
4546
if (!analysis.value) return null
46-
if (analysis.value.types.kind === '@types') {
47+
if (analysis.value.types?.kind === '@types') {
4748
return `/${analysis.value.types.packageName}`
4849
}
4950
return null
@@ -53,59 +54,62 @@ const typesHref = computed(() => {
5354
<template>
5455
<ul v-if="analysis" class="flex items-center gap-1.5 list-none m-0 p-0">
5556
<!-- TypeScript types badge -->
56-
<li>
57-
<component
58-
:is="typesHref ? NuxtLink : 'span'"
59-
:to="typesHref"
60-
class="inline-flex items-center gap-1 px-1.5 py-0.5 font-mono text-xs rounded transition-colors duration-200"
61-
:class="[
62-
hasTypes
63-
? 'text-fg-muted bg-bg-muted border border-border'
64-
: 'text-fg-subtle bg-bg-subtle border border-border-subtle',
65-
typesHref
66-
? 'hover:text-fg hover:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50'
67-
: '',
68-
]"
69-
:title="typesTooltip"
70-
>
71-
<span
72-
class="w-3 h-3"
73-
:class="hasTypes ? 'i-carbon-checkmark' : 'i-carbon-close'"
74-
aria-hidden="true"
75-
/>
76-
{{ $t('package.metrics.types_label') }}
77-
</component>
57+
<li v-if="!props.isBinary">
58+
<AppTooltip :text="typesTooltip">
59+
<component
60+
:is="typesHref ? NuxtLink : 'span'"
61+
:to="typesHref"
62+
class="inline-flex items-center gap-1 px-1.5 py-0.5 font-mono text-xs rounded transition-colors duration-200"
63+
:class="[
64+
hasTypes
65+
? 'text-fg-muted bg-bg-muted border border-border'
66+
: 'text-fg-subtle bg-bg-subtle border border-border-subtle',
67+
typesHref
68+
? 'hover:text-fg hover:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50'
69+
: '',
70+
]"
71+
>
72+
<span
73+
class="w-3 h-3"
74+
:class="hasTypes ? 'i-carbon-checkmark' : 'i-carbon-close'"
75+
aria-hidden="true"
76+
/>
77+
{{ $t('package.metrics.types_label') }}
78+
</component>
79+
</AppTooltip>
7880
</li>
7981

8082
<!-- ESM badge (show with X if missing) -->
8183
<li>
82-
<span
83-
class="inline-flex items-center gap-1 px-1.5 py-0.5 font-mono text-xs rounded transition-colors duration-200"
84-
:class="
85-
hasEsm
86-
? 'text-fg-muted bg-bg-muted border border-border'
87-
: 'text-fg-subtle bg-bg-subtle border border-border-subtle'
88-
"
89-
:title="hasEsm ? $t('package.metrics.esm') : $t('package.metrics.no_esm')"
90-
>
84+
<AppTooltip :text="hasEsm ? $t('package.metrics.esm') : $t('package.metrics.no_esm')">
9185
<span
92-
class="w-3 h-3"
93-
:class="hasEsm ? 'i-carbon-checkmark' : 'i-carbon-close'"
94-
aria-hidden="true"
95-
/>
96-
ESM
97-
</span>
86+
class="inline-flex items-center gap-1 px-1.5 py-0.5 font-mono text-xs rounded transition-colors duration-200"
87+
:class="
88+
hasEsm
89+
? 'text-fg-muted bg-bg-muted border border-border'
90+
: 'text-fg-subtle bg-bg-subtle border border-border-subtle'
91+
"
92+
>
93+
<span
94+
class="w-3 h-3"
95+
:class="hasEsm ? 'i-carbon-checkmark' : 'i-carbon-close'"
96+
aria-hidden="true"
97+
/>
98+
ESM
99+
</span>
100+
</AppTooltip>
98101
</li>
99102

100103
<!-- CJS badge (only show if present) -->
101104
<li v-if="hasCjs">
102-
<span
103-
class="inline-flex items-center gap-1 px-1.5 py-0.5 font-mono text-xs text-fg-muted bg-bg-muted border border-border rounded transition-colors duration-200"
104-
:title="$t('package.metrics.cjs')"
105-
>
106-
<span class="i-carbon-checkmark w-3 h-3" aria-hidden="true" />
107-
CJS
108-
</span>
105+
<AppTooltip :text="$t('package.metrics.cjs')">
106+
<span
107+
class="inline-flex items-center gap-1 px-1.5 py-0.5 font-mono text-xs text-fg-muted bg-bg-muted border border-border rounded transition-colors duration-200"
108+
>
109+
<span class="i-carbon-checkmark w-3 h-3" aria-hidden="true" />
110+
CJS
111+
</span>
112+
</AppTooltip>
109113
</li>
110114
</ul>
111115
</template>

0 commit comments

Comments
 (0)