Skip to content

Commit b2ea9bc

Browse files
43081jautofix-ci[bot]ghostdevv
authored
chore: refactor header shortcuts (#2372)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Willow (GHOST) <git@willow.sh>
1 parent 23dc158 commit b2ea9bc

File tree

4 files changed

+60
-73
lines changed

4 files changed

+60
-73
lines changed

app/app.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ if (import.meta.server) {
5050
const keyboardShortcuts = useKeyboardShortcuts()
5151
const { settings } = useSettings()
5252
53+
initKeyShortcuts()
54+
5355
onKeyDown(
5456
'/',
5557
e => {

app/components/AppHeader.vue

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
<script setup lang="ts">
22
import { LinkBase } from '#components'
33
import type { NavigationConfig, NavigationConfigWithGroups } from '~/types'
4-
import { isEditableElement } from '~/utils/input'
54
import { NPMX_DOCS_SITE } from '#shared/utils/constants'
65
7-
const keyboardShortcuts = useKeyboardShortcuts()
86
const discord = useDiscordLink()
97
108
withDefaults(
@@ -200,22 +198,10 @@ function handleSearchFocus() {
200198
showFullSearch.value = true
201199
}
202200
203-
onKeyStroke(
204-
e => {
205-
if (!keyboardShortcuts.value || isEditableElement(e.target)) {
206-
return
207-
}
208-
209-
for (const link of desktopLinks.value) {
210-
if (link.to && link.keyshortcut && isKeyWithoutModifiers(e, link.keyshortcut)) {
211-
e.preventDefault()
212-
navigateTo(link.to)
213-
break
214-
}
215-
}
216-
},
217-
{ dedupe: true },
218-
)
201+
useShortcuts({
202+
'c': () => ({ name: 'compare' }),
203+
',': () => ({ name: 'settings' }),
204+
})
219205
</script>
220206

221207
<template>

app/components/Package/Header.vue

Lines changed: 7 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<script setup lang="ts">
22
import type { RouteLocationRaw } from 'vue-router'
33
import { SCROLL_TO_TOP_THRESHOLD } from '~/composables/useScrollToTop'
4-
import { isEditableElement } from '~/utils/input'
54
65
const props = defineProps<{
76
pkg?: Pick<SlimPackument, 'name' | 'versions' | 'dist-tags'> | null
@@ -72,7 +71,6 @@ function hasProvenance(version: PackumentVersion | null): boolean {
7271
return !!(version.dist as { attestations?: unknown }).attestations
7372
}
7473
75-
const router = useRouter()
7674
// Docs URL: use our generated API docs
7775
const docsLink = computed(() => {
7876
if (!props.resolvedVersion) return null
@@ -120,59 +118,13 @@ const diffLink = computed((): RouteLocationRaw | null => {
120118
return diffRoute(props.pkg.name, props.resolvedVersion, props.latestVersion.version)
121119
})
122120
123-
const keyboardShortcuts = useKeyboardShortcuts()
124-
125-
onKeyStroke(
126-
e => keyboardShortcuts.value && isKeyWithoutModifiers(e, '.') && !isEditableElement(e.target),
127-
e => {
128-
if (codeLink.value === null) return
129-
e.preventDefault()
130-
131-
navigateTo(codeLink.value)
132-
},
133-
{ dedupe: true },
134-
)
135-
136-
onKeyStroke(
137-
e => keyboardShortcuts.value && isKeyWithoutModifiers(e, 'm') && !isEditableElement(e.target),
138-
e => {
139-
if (mainLink.value === null) return
140-
e.preventDefault()
141-
142-
navigateTo(mainLink.value)
143-
},
144-
{ dedupe: true },
145-
)
146-
147-
onKeyStroke(
148-
e => keyboardShortcuts.value && isKeyWithoutModifiers(e, 'd') && !isEditableElement(e.target),
149-
e => {
150-
if (!docsLink.value) return
151-
e.preventDefault()
152-
navigateTo(docsLink.value)
153-
},
154-
{ dedupe: true },
155-
)
156-
157-
onKeyStroke(
158-
e => keyboardShortcuts.value && isKeyWithoutModifiers(e, 'c') && !isEditableElement(e.target),
159-
e => {
160-
if (!props.pkg) return
161-
e.preventDefault()
162-
router.push({ name: 'compare', query: { packages: props.pkg.name } })
163-
},
164-
{ dedupe: true },
165-
)
166-
167-
onKeyStroke(
168-
e => keyboardShortcuts.value && isKeyWithoutModifiers(e, 'f') && !isEditableElement(e.target),
169-
e => {
170-
if (diffLink.value === null) return
171-
e.preventDefault()
172-
navigateTo(diffLink.value)
173-
},
174-
{ dedupe: true },
175-
)
121+
useShortcuts({
122+
'.': () => codeLink.value,
123+
'm': () => mainLink.value,
124+
'd': () => docsLink.value,
125+
'c': () => props.pkg && { name: 'compare' as const, query: { packages: props.pkg.name } },
126+
'f': () => diffLink.value,
127+
})
176128
177129
const fundingUrl = computed(() => {
178130
let funding = props.displayVersion?.funding

app/composables/useShortcuts.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { RouteLocationRaw } from 'vue-router'
2+
import { isEditableElement, isKeyWithoutModifiers } from '~/utils/input'
3+
4+
type ShortcutTarget = RouteLocationRaw | null | undefined
5+
type ShortcutTargetFactory = () => ShortcutTarget
6+
7+
const registry = new Map<string, ShortcutTargetFactory[]>()
8+
9+
export function initKeyShortcuts() {
10+
const keyboardShortcuts = useKeyboardShortcuts()
11+
12+
onKeyStroke(
13+
e => !e.repeat && keyboardShortcuts.value && !isEditableElement(e.target),
14+
e => {
15+
for (const [key, stack] of registry) {
16+
if (!isKeyWithoutModifiers(e, key)) continue
17+
const getTarget = stack.at(-1)
18+
if (!getTarget) continue
19+
const target = getTarget()
20+
if (!target) return
21+
e.preventDefault()
22+
navigateTo(target)
23+
return
24+
}
25+
},
26+
)
27+
}
28+
29+
export function useShortcuts(map: Record<string, () => ShortcutTarget>) {
30+
if (!import.meta.client) return
31+
32+
for (const [key, fn] of Object.entries(map)) {
33+
const entry = registry.get(key) ?? []
34+
if (entry.includes(fn)) continue
35+
entry.push(fn)
36+
registry.set(key, entry)
37+
}
38+
39+
onScopeDispose(() => {
40+
for (const [key, fn] of Object.entries(map)) {
41+
const stack = registry.get(key)
42+
if (!stack) continue
43+
const idx = stack.lastIndexOf(fn)
44+
if (idx !== -1) stack.splice(idx, 1)
45+
}
46+
})
47+
}

0 commit comments

Comments
 (0)