Skip to content

Commit 8986ea1

Browse files
committed
chore: conflicts
2 parents 0b542da + 1975cb7 commit 8986ea1

43 files changed

Lines changed: 4861 additions & 85 deletions

Some content is hidden

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

CONTRIBUTING.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ The connector will check your npm authentication, generate a connection token, a
105105

106106
## Code style
107107

108+
When committing changes, try to keep an eye out for unintended formatting updates. These can make a pull request look noisier than it really is and slow down the review process. Sometimes IDEs automatically reformat files on save, which can unintentionally introduce extra changes.
109+
110+
To help with this, the project uses `oxfmt` to handle formatting via a pre-commit hook. The hook will automatically reformat files when needed. If something can’t be fixed automatically, it will let you know what needs to be updated before you can commit.
111+
112+
If you want to get ahead of any formatting issues, you can also run `pnpm lint:fix` before committing to fix formatting across the whole project.
113+
108114
### Typescript
109115

110116
- We care about good types – never cast things to `any` 💪

app/components/AppHeader.vue

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,19 @@ function expandMobileSearch() {
3030
})
3131
}
3232
33+
watch(
34+
isOnSearchPage,
35+
visible => {
36+
if (!visible) return
37+
38+
searchBoxRef.value?.focus()
39+
nextTick(() => {
40+
searchBoxRef.value?.focus()
41+
})
42+
},
43+
{ flush: 'sync' },
44+
)
45+
3346
function handleSearchBlur() {
3447
showFullSearch.value = false
3548
// Collapse expanded search on mobile after blur (with delay for click handling)
@@ -140,6 +153,15 @@ onKeyStroke(
140153

141154
<!-- End: Desktop nav items + Mobile menu button -->
142155
<div class="flex-shrink-0 flex items-center gap-4 sm:gap-6">
156+
<!-- Desktop: Compare link -->
157+
<NuxtLink
158+
to="/compare"
159+
class="hidden sm:inline-flex link-subtle font-mono text-sm items-center gap-1.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50 rounded"
160+
>
161+
<span class="i-carbon:compare w-4 h-4" aria-hidden="true" />
162+
{{ $t('nav.compare') }}
163+
</NuxtLink>
164+
143165
<!-- Desktop: Settings link -->
144166
<NuxtLink
145167
to="/settings"

app/components/CollapsibleSection.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { ref, computed } from 'vue'
2+
import { shallowRef, computed } from 'vue'
33
44
interface Props {
55
title: string
@@ -19,7 +19,7 @@ const buttonId = `${props.id}-collapsible-button`
1919
const contentId = `${props.id}-collapsible-content`
2020
const headingId = `${props.id}-heading`
2121
22-
const isOpen = ref(true)
22+
const isOpen = shallowRef(true)
2323
2424
onPrehydrate(() => {
2525
const settings = JSON.parse(localStorage.getItem('npmx-settings') || '{}')

app/components/ConnectorModal.vue

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
<script setup lang="ts">
2+
const open = defineModel<boolean>('open', { default: false })
3+
4+
const { isConnected, isConnecting, npmUser, error, hasOperations, connect, disconnect } =
5+
useConnector()
6+
7+
const tokenInput = shallowRef('')
8+
const portInput = shallowRef('31415')
9+
const { copied, copy } = useClipboard({ copiedDuring: 2000 })
10+
11+
const hasAttemptedConnect = shallowRef(false)
12+
13+
async function handleConnect() {
14+
hasAttemptedConnect.value = true
15+
const port = Number.parseInt(portInput.value, 10) || 31415
16+
const success = await connect(tokenInput.value.trim(), port)
17+
if (success) {
18+
tokenInput.value = ''
19+
open.value = false
20+
}
21+
}
22+
23+
function handleDisconnect() {
24+
disconnect()
25+
}
26+
27+
function copyCommand() {
28+
let command = executeNpmxConnectorCommand.value
29+
if (portInput.value !== '31415') {
30+
command += ` --port ${portInput.value}`
31+
}
32+
copy(command)
33+
}
34+
35+
const selectedPM = useSelectedPackageManager()
36+
37+
const executeNpmxConnectorCommand = computed(() => {
38+
return getExecuteCommand({
39+
packageName: 'npmx-connector',
40+
packageManager: selectedPM.value,
41+
})
42+
})
43+
44+
// Reset form when modal opens
45+
watch(open, isOpen => {
46+
if (isOpen) {
47+
tokenInput.value = ''
48+
hasAttemptedConnect.value = false
49+
}
50+
})
51+
</script>
52+
53+
<template>
54+
<Teleport to="body">
55+
<Transition
56+
enter-active-class="transition-opacity duration-200"
57+
leave-active-class="transition-opacity duration-200"
58+
enter-from-class="opacity-0"
59+
leave-to-class="opacity-0"
60+
>
61+
<div v-if="open" class="fixed inset-0 z-50 flex items-center justify-center p-4">
62+
<!-- Backdrop -->
63+
<button
64+
type="button"
65+
class="absolute inset-0 bg-black/60 cursor-default"
66+
:aria-label="$t('common.close_modal')"
67+
@click="open = false"
68+
/>
69+
70+
<!-- Modal -->
71+
<div
72+
class="relative w-full bg-bg border border-border rounded-lg shadow-xl max-h-[90vh] overflow-y-auto overscroll-contain"
73+
:class="isConnected && hasOperations ? 'max-w-2xl' : 'max-w-md'"
74+
role="dialog"
75+
aria-modal="true"
76+
aria-labelledby="connector-modal-title"
77+
>
78+
<div class="p-6">
79+
<div class="flex items-center justify-between mb-6">
80+
<h2 id="connector-modal-title" class="font-mono text-lg font-medium">
81+
{{ $t('connector.modal.title') }}
82+
</h2>
83+
<button
84+
type="button"
85+
class="text-fg-subtle hover:text-fg transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 rounded"
86+
:aria-label="$t('common.close')"
87+
@click="open = false"
88+
>
89+
<span class="i-carbon:close block w-5 h-5" aria-hidden="true" />
90+
</button>
91+
</div>
92+
93+
<!-- Connected state -->
94+
<div v-if="isConnected" class="space-y-4">
95+
<div class="flex items-center gap-3 p-4 bg-bg-subtle border border-border rounded-lg">
96+
<span class="w-3 h-3 rounded-full bg-green-500" aria-hidden="true" />
97+
<div>
98+
<p class="font-mono text-sm text-fg">{{ $t('connector.modal.connected') }}</p>
99+
<p v-if="npmUser" class="font-mono text-xs text-fg-muted">
100+
{{ $t('connector.modal.connected_as_user', { user: npmUser }) }}
101+
</p>
102+
</div>
103+
</div>
104+
105+
<!-- Operations Queue -->
106+
<OperationsQueue />
107+
108+
<div v-if="!hasOperations" class="text-sm text-fg-muted">
109+
{{ $t('connector.modal.connected_hint') }}
110+
</div>
111+
112+
<button
113+
type="button"
114+
class="w-full px-4 py-2 font-mono text-sm text-fg-muted bg-bg-subtle border border-border rounded-md transition-colors duration-200 hover:text-fg hover:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
115+
@click="handleDisconnect"
116+
>
117+
{{ $t('connector.modal.disconnect') }}
118+
</button>
119+
</div>
120+
121+
<!-- Disconnected state -->
122+
<form v-else class="space-y-4" @submit.prevent="handleConnect">
123+
<!-- Contributor-only notice -->
124+
<div class="p-3 bg-amber-500/10 border border-amber-500/30 rounded-lg">
125+
<div class="space-y-2">
126+
<span
127+
class="inline-block px-2 py-0.5 text-xs font-bold uppercase tracking-wider bg-amber-500/20 text-amber-400 rounded"
128+
>
129+
{{ $t('connector.modal.contributor_badge') }}
130+
</span>
131+
<p class="text-sm text-fg-muted">
132+
<i18n-t keypath="connector.modal.contributor_notice">
133+
<template #link>
134+
<a
135+
href="https://github.com/npmx-dev/npmx.dev/blob/main/CONTRIBUTING.md#local-connector-cli"
136+
target="_blank"
137+
rel="noopener noreferrer"
138+
class="text-amber-400 hover:underline"
139+
>
140+
{{ $t('connector.modal.contributor_link') }}
141+
</a>
142+
</template>
143+
</i18n-t>
144+
</p>
145+
</div>
146+
</div>
147+
148+
<p class="text-sm text-fg-muted">
149+
{{ $t('connector.modal.run_hint') }}
150+
</p>
151+
152+
<div
153+
class="flex items-center p-3 bg-bg-muted border border-border rounded-lg font-mono text-sm"
154+
>
155+
<span class="text-fg-subtle">$</span>
156+
<span class="text-fg-subtle ms-2">pnpm npmx-connector</span>
157+
<button
158+
type="button"
159+
:aria-label="
160+
copied ? $t('connector.modal.copied') : $t('connector.modal.copy_command')
161+
"
162+
class="ms-auto text-fg-subtle hover:text-fg transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 rounded"
163+
@click="copy('pnpm npmx-connector')"
164+
>
165+
<span v-if="!copied" class="i-carbon:copy block w-5 h-5" aria-hidden="true" />
166+
<span
167+
v-else
168+
class="i-carbon:checkmark block w-5 h-5 text-green-500"
169+
aria-hidden="true"
170+
/>
171+
</button>
172+
</div>
173+
174+
<!-- TODO: Uncomment when npmx-connector is published to npm
175+
<div
176+
class="flex items-center p-3 bg-bg-muted border border-border rounded-lg font-mono text-sm"
177+
>
178+
<span class="text-fg-subtle">$</span>
179+
<span class="text-fg-subtle ms-2">{{ executeNpmxConnectorCommand }}</span>
180+
<div class="ms-auto flex items-center gap-2">
181+
<PackageManagerSelect />
182+
183+
<button
184+
type="button"
185+
:aria-label="
186+
copied ? $t('connector.modal.copied') : $t('connector.modal.copy_command')
187+
"
188+
class="ms-auto text-fg-subtle hover:text-fg transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 rounded"
189+
@click="copyCommand"
190+
>
191+
<span v-if="!copied" class="i-carbon:copy block w-5 h-5" aria-hidden="true" />
192+
<span
193+
v-else
194+
class="i-carbon:checkmark block w-5 h-5 text-green-500"
195+
aria-hidden="true"
196+
/>
197+
</button>
198+
</div>
199+
</div>
200+
-->
201+
202+
<p class="text-sm text-fg-muted">{{ $t('connector.modal.paste_token') }}</p>
203+
204+
<div class="space-y-3">
205+
<div>
206+
<label
207+
for="connector-token"
208+
class="block text-xs text-fg-subtle uppercase tracking-wider mb-1.5"
209+
>
210+
{{ $t('connector.modal.token_label') }}
211+
</label>
212+
<input
213+
id="connector-token"
214+
v-model="tokenInput"
215+
type="password"
216+
name="connector-token"
217+
:placeholder="$t('connector.modal.token_placeholder')"
218+
v-bind="noCorrect"
219+
class="w-full px-3 py-2 font-mono text-sm bg-bg-subtle border border-border rounded-md text-fg placeholder:text-fg-subtle transition-colors duration-200 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50"
220+
/>
221+
</div>
222+
223+
<details class="text-sm">
224+
<summary
225+
class="text-fg-subtle cursor-pointer hover:text-fg-muted transition-colors duration-200"
226+
>
227+
{{ $t('connector.modal.advanced') }}
228+
</summary>
229+
<div class="mt-3">
230+
<label
231+
for="connector-port"
232+
class="block text-xs text-fg-subtle uppercase tracking-wider mb-1.5"
233+
>
234+
{{ $t('connector.modal.port_label') }}
235+
</label>
236+
<input
237+
id="connector-port"
238+
v-model="portInput"
239+
type="text"
240+
name="connector-port"
241+
inputmode="numeric"
242+
autocomplete="off"
243+
class="w-full px-3 py-2 font-mono text-sm bg-bg-subtle border border-border rounded-md text-fg transition-colors duration-200 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50"
244+
/>
245+
</div>
246+
</details>
247+
</div>
248+
249+
<!-- Error message (only show after user explicitly clicks Connect) -->
250+
<div
251+
v-if="error && hasAttemptedConnect"
252+
role="alert"
253+
class="p-3 text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-md"
254+
>
255+
{{ error }}
256+
</div>
257+
258+
<!-- Warning message -->
259+
<div
260+
role="alert"
261+
class="p-3 text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-md"
262+
>
263+
<p class="font-mono text-sm text-fg font-bold">
264+
{{ $t('connector.modal.warning') }}
265+
</p>
266+
<p class="text-sm text-fg-muted">
267+
{{ $t('connector.modal.warning_text') }}
268+
</p>
269+
</div>
270+
271+
<button
272+
type="submit"
273+
:disabled="!tokenInput.trim() || isConnecting"
274+
class="w-full px-4 py-2 font-mono text-sm text-bg bg-fg rounded-md transition-all duration-200 hover:bg-fg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 focus-visible:ring-offset-2 focus-visible:ring-offset-bg"
275+
>
276+
{{
277+
isConnecting ? $t('connector.modal.connecting') : $t('connector.modal.connect')
278+
}}
279+
</button>
280+
</form>
281+
</div>
282+
</div>
283+
</div>
284+
</Transition>
285+
</Teleport>
286+
</template>

app/components/MobileMenu.vue

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ watch(isOpen, open => (isLocked.value = open))
102102
{{ $t('footer.about') }}
103103
</NuxtLink>
104104

105+
<NuxtLink
106+
to="/compare"
107+
class="flex items-center gap-3 px-3 py-3 rounded-md font-mono text-sm text-fg hover:bg-bg-subtle transition-colors duration-200"
108+
@click="closeMenu"
109+
>
110+
<span class="i-carbon:compare w-5 h-5 text-fg-muted" aria-hidden="true" />
111+
{{ $t('nav.compare') }}
112+
</NuxtLink>
113+
105114
<NuxtLink
106115
to="/settings"
107116
class="flex items-center gap-3 px-3 py-3 rounded-md font-mono text-sm text-fg hover:bg-bg-subtle transition-colors duration-200"
@@ -176,7 +185,7 @@ watch(isOpen, open => (isLocked.value = open))
176185
rel="noopener noreferrer"
177186
class="flex items-center gap-3 px-3 py-3 rounded-md font-mono text-sm text-fg hover:bg-bg-subtle transition-colors duration-200"
178187
>
179-
<span class="i-carbon:logo-x w-5 h-5 text-fg-muted" aria-hidden="true" />
188+
<span class="i-simple-icons:bluesky w-5 h-5 text-fg-muted" aria-hidden="true" />
180189
{{ $t('footer.social') }}
181190
<span
182191
class="i-carbon:launch rtl-flip w-3 h-3 ms-auto text-fg-subtle"

app/components/PackageSkeleton.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@
150150
</div>
151151

152152
<!-- Sidebar: order-1 lg:order-2 space-y-8 -->
153-
<aside class="order-1 lg:order-2 space-y-8">
153+
<div class="order-1 lg:order-2 space-y-8">
154154
<!-- Maintainers -->
155155
<section>
156156
<h2
@@ -249,7 +249,7 @@
249249
</li>
250250
</ul>
251251
</section>
252-
</aside>
252+
</div>
253253
</div>
254254
</article>
255255
</template>

0 commit comments

Comments
 (0)