Skip to content

Commit 98381be

Browse files
committed
Merge branch 'main' into feat/display-author-profile-picture
# Conflicts: # app/pages/~[username]/index.vue # shared/utils/constants.ts
2 parents 4e1c478 + ac82a5c commit 98381be

23 files changed

Lines changed: 1054 additions & 173 deletions

app/components/PackageDependencies.vue

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,15 @@ const sortedOptionalDependencies = computed(() => {
7979
<li
8080
v-for="[dep, version] in sortedDependencies.slice(0, depsExpanded ? undefined : 10)"
8181
:key="dep"
82-
class="flex items-center justify-start py-1 text-sm gap-2"
82+
class="flex items-center justify-between py-1 text-sm gap-2"
8383
>
8484
<NuxtLink
8585
:to="{ name: 'package', params: { package: dep.split('/') } }"
86-
class="font-mono text-fg-muted hover:text-fg transition-colors duration-200 truncate min-w-0"
86+
class="font-mono text-fg-muted hover:text-fg transition-colors duration-200 truncate min-w-0 flex-1"
8787
>
8888
{{ dep }}
8989
</NuxtLink>
90-
<span class="flex items-center gap-1">
90+
<span class="flex items-center gap-1 max-w-[40%]">
9191
<span
9292
v-if="outdatedDeps[dep]"
9393
class="shrink-0"
@@ -97,7 +97,6 @@ const sortedOptionalDependencies = computed(() => {
9797
>
9898
<span class="i-carbon:warning-alt w-3 h-3 block" />
9999
</span>
100-
<span aria-hidden="true" class="flex-shrink-1 flex-grow-1" />
101100
<NuxtLink
102101
v-if="getVulnerableDepInfo(dep)"
103102
:to="{
@@ -174,9 +173,9 @@ const sortedOptionalDependencies = computed(() => {
174173
<li
175174
v-for="peer in sortedPeerDependencies.slice(0, peerDepsExpanded ? undefined : 10)"
176175
:key="peer.name"
177-
class="flex items-center justify-start py-1 text-sm gap-2"
176+
class="flex items-center justify-between py-1 text-sm gap-1 min-w-0"
178177
>
179-
<div class="flex items-center gap-2 min-w-0">
178+
<div class="flex items-center gap-1 min-w-0 flex-1">
180179
<NuxtLink
181180
:to="{
182181
name: 'package',
@@ -194,13 +193,12 @@ const sortedOptionalDependencies = computed(() => {
194193
{{ $t('package.dependencies.optional') }}
195194
</span>
196195
</div>
197-
<span aria-hidden="true" class="flex-shrink-1 flex-grow-1" />
198196
<NuxtLink
199197
:to="{
200198
name: 'package',
201199
params: { package: [...peer.name.split('/'), 'v', peer.version] },
202200
}"
203-
class="font-mono text-xs text-fg-subtle max-w-[40%] text-end truncate"
201+
class="font-mono text-xs text-fg-subtle max-w-[40%] truncate"
204202
:title="peer.version"
205203
>
206204
{{ peer.version }}
@@ -241,21 +239,20 @@ const sortedOptionalDependencies = computed(() => {
241239
optionalDepsExpanded ? undefined : 10,
242240
)"
243241
:key="dep"
244-
class="flex items-center justify-start py-1 text-sm gap-2"
242+
class="flex items-center justify-between py-1 text-sm gap-2"
245243
>
246244
<NuxtLink
247245
:to="{ name: 'package', params: { package: dep.split('/') } }"
248-
class="font-mono text-fg-muted hover:text-fg transition-colors duration-200 truncate min-w-0"
246+
class="font-mono text-fg-muted hover:text-fg transition-colors duration-200 truncate min-w-0 flex-1"
249247
>
250248
{{ dep }}
251249
</NuxtLink>
252-
<span aria-hidden="true" class="flex-shrink-1 flex-grow-1" />
253250
<NuxtLink
254251
:to="{
255252
name: 'package',
256253
params: { package: [...dep.split('/'), 'v', version] },
257254
}"
258-
class="font-mono text-xs text-fg-subtle max-w-[50%] text-end truncate"
255+
class="font-mono text-xs text-fg-subtle max-w-[40%] text-end truncate"
259256
:title="version"
260257
>
261258
{{ version }}

app/components/PackageDownloadAnalytics.vue

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,21 @@ const { width } = useElementSize(rootEl)
2121
2222
const chartKey = ref(0)
2323
24+
function nextAnimationFrame(): Promise<void> {
25+
return new Promise(resolve => {
26+
requestAnimationFrame(() => resolve())
27+
})
28+
}
29+
2430
onMounted(async () => {
2531
rootEl.value = document.documentElement
2632
resolvedMode.value = colorMode.value === 'dark' ? 'dark' : 'light'
27-
requestAnimationFrame(() => {
28-
requestAnimationFrame(() => {
29-
chartKey.value += 1
30-
})
31-
})
33+
34+
// If the chart is painted too early, built-in auto-sizing does not adapt to the final container size
35+
await nextAnimationFrame()
36+
await nextAnimationFrame()
37+
await nextAnimationFrame()
38+
chartKey.value += 1
3239
})
3340
3441
const { colors } = useCssVariables(
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script setup lang="ts">
2+
import type { SkillListItem } from '#shared/types'
3+
4+
defineProps<{
5+
skills: SkillListItem[]
6+
packageName: string
7+
version?: string
8+
}>()
9+
10+
const skillsModal = useModal('skills-modal')
11+
</script>
12+
13+
<template>
14+
<section v-if="skills.length" id="skills" class="scroll-mt-20">
15+
<h2 class="text-xs text-fg-subtle uppercase tracking-wider mb-3">
16+
{{ $t('package.skills.title') }}
17+
</h2>
18+
<button
19+
type="button"
20+
class="w-full flex items-center gap-2 px-3 py-2 text-sm font-mono bg-bg-muted border border-border rounded-md hover:border-border-hover hover:bg-bg-elevated focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-hover transition-colors duration-200"
21+
@click="skillsModal.open()"
22+
>
23+
<span class="i-custom:agent-skills w-4 h-4 shrink-0 text-fg-muted" aria-hidden="true" />
24+
<span class="text-fg-muted">{{
25+
$t('package.skills.skills_available', { count: skills.length }, skills.length)
26+
}}</span>
27+
</button>
28+
</section>
29+
</template>
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
<script setup lang="ts">
2+
import type { SkillListItem } from '#shared/types'
3+
4+
const props = defineProps<{
5+
skills: SkillListItem[]
6+
packageName: string
7+
version?: string
8+
}>()
9+
10+
function getSkillSourceUrl(skill: SkillListItem): string {
11+
const base = `/code/${props.packageName}`
12+
const versionPath = props.version ? `/v/${props.version}` : ''
13+
return `${base}${versionPath}/skills/${skill.dirName}/SKILL.md`
14+
}
15+
16+
const expandedSkills = ref<Set<string>>(new Set())
17+
18+
function toggleSkill(dirName: string) {
19+
if (expandedSkills.value.has(dirName)) {
20+
expandedSkills.value.delete(dirName)
21+
} else {
22+
expandedSkills.value.add(dirName)
23+
}
24+
expandedSkills.value = new Set(expandedSkills.value)
25+
}
26+
27+
type InstallMethod = 'skills-npm' | 'skills-cli'
28+
const selectedMethod = ref<InstallMethod>('skills-npm')
29+
30+
const baseUrl = computed(() =>
31+
typeof window !== 'undefined' ? window.location.origin : 'https://npmx.dev',
32+
)
33+
34+
const installCommand = computed(() => {
35+
if (!props.skills.length) return null
36+
return `npx skills add ${baseUrl.value}/${props.packageName}`
37+
})
38+
39+
const { copied, copy } = useClipboard({ copiedDuring: 2000 })
40+
const copyCommand = () => installCommand.value && copy(installCommand.value)
41+
42+
function getWarningTooltip(skill: SkillListItem): string | undefined {
43+
if (!skill.warnings?.length) return undefined
44+
return skill.warnings.map(w => w.message).join(', ')
45+
}
46+
</script>
47+
48+
<template>
49+
<Modal :modal-title="$t('package.skills.title')" id="skills-modal" class="sm:max-w-2xl">
50+
<!-- Install header with tabs -->
51+
<div class="flex flex-wrap items-center justify-between gap-2 mb-3">
52+
<h3 class="text-xs text-fg-subtle uppercase tracking-wider">
53+
{{ $t('package.skills.install') }}
54+
</h3>
55+
<div
56+
class="flex items-center gap-1 p-0.5 bg-bg-subtle border border-border-subtle rounded-md"
57+
role="tablist"
58+
:aria-label="$t('package.skills.installation_method')"
59+
>
60+
<button
61+
role="tab"
62+
:aria-selected="selectedMethod === 'skills-npm'"
63+
:tabindex="selectedMethod === 'skills-npm' ? 0 : -1"
64+
type="button"
65+
class="px-2 py-1 font-mono text-xs rounded transition-colors duration-150 border border-solid focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
66+
:class="
67+
selectedMethod === 'skills-npm'
68+
? 'bg-bg border-border shadow-sm text-fg'
69+
: 'border-transparent text-fg-subtle hover:text-fg'
70+
"
71+
@click="selectedMethod = 'skills-npm'"
72+
>
73+
skills-npm
74+
</button>
75+
<button
76+
role="tab"
77+
:aria-selected="selectedMethod === 'skills-cli'"
78+
:tabindex="selectedMethod === 'skills-cli' ? 0 : -1"
79+
type="button"
80+
class="px-2 py-1 font-mono text-xs rounded transition-colors duration-150 border border-solid focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
81+
:class="
82+
selectedMethod === 'skills-cli'
83+
? 'bg-bg border-border shadow-sm text-fg'
84+
: 'border-transparent text-fg-subtle hover:text-fg'
85+
"
86+
@click="selectedMethod = 'skills-cli'"
87+
>
88+
skills CLI
89+
</button>
90+
</div>
91+
</div>
92+
93+
<!-- skills-npm: compatible -->
94+
<div
95+
v-if="selectedMethod === 'skills-npm'"
96+
class="flex items-center justify-between gap-2 px-3 py-2.5 sm:px-4 bg-bg-subtle border border-border rounded-lg mb-5"
97+
>
98+
<i18n-t keypath="package.skills.compatible_with" tag="span" class="text-sm text-fg-muted">
99+
<template #tool>
100+
<code class="font-mono text-fg">skills-npm</code>
101+
</template>
102+
</i18n-t>
103+
<a
104+
href="/skills-npm"
105+
class="inline-flex items-center gap-1 text-xs text-fg-subtle hover:text-fg transition-colors shrink-0"
106+
>
107+
{{ $t('package.skills.learn_more') }}
108+
<span class="i-carbon:arrow-right w-3 h-3" />
109+
</a>
110+
</div>
111+
112+
<!-- skills CLI: terminal command -->
113+
<div
114+
v-else-if="installCommand"
115+
class="bg-bg-subtle border border-border rounded-lg overflow-hidden mb-5"
116+
>
117+
<div class="flex gap-1.5 px-3 pt-2 sm:px-4 sm:pt-3">
118+
<span class="w-2.5 h-2.5 rounded-full bg-fg-subtle" />
119+
<span class="w-2.5 h-2.5 rounded-full bg-fg-subtle" />
120+
<span class="w-2.5 h-2.5 rounded-full bg-fg-subtle" />
121+
</div>
122+
<div class="px-3 pt-2 pb-3 sm:px-4 sm:pt-3 sm:pb-4 overflow-x-auto">
123+
<div class="relative group/cmd">
124+
<code class="font-mono text-sm whitespace-nowrap">
125+
<span class="text-fg-subtle select-none">$ </span>
126+
<span class="text-fg">npx </span>
127+
<span class="text-fg-muted">skills add {{ baseUrl }}/{{ packageName }}</span>
128+
</code>
129+
<button
130+
type="button"
131+
class="absolute top-0 right-0 px-2 py-0.5 font-mono text-xs text-fg-muted bg-bg-subtle/80 border border-border rounded transition-colors duration-200 opacity-0 group-hover/cmd:opacity-100 hover:(text-fg border-border-hover) active:scale-95 focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
132+
:aria-label="$t('package.get_started.copy_command')"
133+
@click.stop="copyCommand"
134+
>
135+
<span aria-live="polite">{{ copied ? $t('common.copied') : $t('common.copy') }}</span>
136+
</button>
137+
</div>
138+
</div>
139+
</div>
140+
141+
<!-- Skills list -->
142+
<div class="flex items-baseline justify-between gap-2 mb-2">
143+
<h3 class="text-xs text-fg-subtle uppercase tracking-wider">
144+
{{ $t('package.skills.available_skills') }}
145+
</h3>
146+
<span class="text-xs text-fg-subtle/60">{{ $t('package.skills.click_to_expand') }}</span>
147+
</div>
148+
<ul class="space-y-0.5 list-none m-0 p-0">
149+
<li v-for="skill in skills" :key="skill.dirName">
150+
<button
151+
type="button"
152+
class="w-full flex items-center gap-2 py-1.5 text-start rounded transition-colors hover:bg-bg-subtle focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
153+
:aria-expanded="expandedSkills.has(skill.dirName)"
154+
@click="toggleSkill(skill.dirName)"
155+
>
156+
<span
157+
class="i-carbon:chevron-right w-3 h-3 text-fg-subtle shrink-0 transition-transform duration-200"
158+
:class="{ 'rotate-90': expandedSkills.has(skill.dirName) }"
159+
aria-hidden="true"
160+
/>
161+
<span class="font-mono text-sm text-fg-muted">{{ skill.name }}</span>
162+
<span
163+
v-if="skill.warnings?.length"
164+
class="i-carbon:warning w-3.5 h-3.5 text-amber-500 shrink-0"
165+
:title="getWarningTooltip(skill)"
166+
/>
167+
</button>
168+
169+
<!-- Expandable details -->
170+
<div
171+
class="grid transition-[grid-template-rows] duration-200 ease-out"
172+
:class="expandedSkills.has(skill.dirName) ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'"
173+
>
174+
<div class="overflow-hidden">
175+
<div class="ps-5.5 pe-2 pb-2 pt-1 space-y-1.5">
176+
<!-- Description -->
177+
<p v-if="skill.description" class="text-sm text-fg-subtle">
178+
{{ skill.description }}
179+
</p>
180+
<p v-else class="text-sm text-fg-subtle/50 italic">
181+
{{ $t('package.skills.no_description') }}
182+
</p>
183+
184+
<!-- File counts & warnings -->
185+
<div class="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs">
186+
<span v-if="skill.fileCounts?.scripts" class="text-fg-subtle">
187+
<span class="i-carbon:script size-3 inline-block align-[-2px] me-0.5" />{{
188+
$t(
189+
'package.skills.file_counts.scripts',
190+
{ count: skill.fileCounts.scripts },
191+
skill.fileCounts.scripts,
192+
)
193+
}}
194+
</span>
195+
<span v-if="skill.fileCounts?.references" class="text-fg-subtle">
196+
<span class="i-carbon:document size-3 inline-block align-[-2px] me-0.5" />{{
197+
$t(
198+
'package.skills.file_counts.refs',
199+
{ count: skill.fileCounts.references },
200+
skill.fileCounts.references,
201+
)
202+
}}
203+
</span>
204+
<span v-if="skill.fileCounts?.assets" class="text-fg-subtle">
205+
<span class="i-carbon:image size-3 inline-block align-[-2px] me-0.5" />{{
206+
$t(
207+
'package.skills.file_counts.assets',
208+
{ count: skill.fileCounts.assets },
209+
skill.fileCounts.assets,
210+
)
211+
}}
212+
</span>
213+
<template v-for="warning in skill.warnings" :key="warning.message">
214+
<span class="text-amber-500">
215+
<span class="i-carbon:warning size-3 inline-block align-[-2px] me-0.5" />{{
216+
warning.message
217+
}}
218+
</span>
219+
</template>
220+
</div>
221+
222+
<!-- Source link -->
223+
<NuxtLink
224+
:to="getSkillSourceUrl(skill)"
225+
class="inline-flex items-center gap-1 text-xs text-fg-subtle hover:text-fg transition-colors"
226+
@click.stop
227+
>
228+
<span class="i-carbon:code size-3" />{{ $t('package.skills.view_source') }}
229+
</NuxtLink>
230+
</div>
231+
</div>
232+
</div>
233+
</li>
234+
</ul>
235+
</Modal>
236+
</template>

app/composables/useConnector.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { PendingOperation, OperationStatus, OperationType } from '../../cli/src/types'
2+
import { $fetch } from 'ofetch'
23

34
export interface NewOperation {
45
type: OperationType

0 commit comments

Comments
 (0)