refactor(frontend): centralize menu shortcuts

This commit is contained in:
Sergey Shkuratov 2026-06-03 12:31:50 +03:00
parent 220af19a39
commit 6973089b26
6 changed files with 84 additions and 21 deletions

View File

@ -63,7 +63,7 @@
</Modal>
<BaseButton
v-shortcut="'Shift+Slash'"
v-shortcut="SHORTCUTS.showKeyboardShortcuts.binding"
class="keyboard-shortcuts-button d-print-none"
@click="showKeyboardShortcuts()"
>
@ -79,6 +79,7 @@
import {watch, computed, onBeforeUnmount} from 'vue'
import {useRoute, useRouter} from 'vue-router'
import {SHORTCUTS} from '@/constants/shortcuts'
import Navigation from '@/components/home/Navigation.vue'
import QuickActions from '@/components/quick-actions/QuickActions.vue'
import BaseButton from '@/components/base/BaseButton.vue'

View File

@ -1,6 +1,6 @@
<template>
<BaseButton
v-shortcut="'Mod+KeyE'"
v-shortcut="SHORTCUTS.toggleMenu.binding"
class="menu-show-button"
:title="$t('keyboardShortcuts.toggleMenu')"
:aria-label="menuActive ? $t('misc.hideMenu') : $t('misc.showMenu')"
@ -11,6 +11,8 @@
<script setup lang="ts">
import {computed} from 'vue'
import {SHORTCUTS} from '@/constants/shortcuts'
import {useBaseStore} from '@/stores/base'
import BaseButton from '@/components/base/BaseButton.vue'

View File

@ -18,7 +18,7 @@
<menu class="menu-list other-menu-items">
<li>
<RouterLink
v-shortcut="'KeyG KeyO'"
v-shortcut="SHORTCUTS.navigation.overview.binding"
:to="{ name: 'home'}"
>
<span class="menu-item-icon icon">
@ -29,7 +29,7 @@
</li>
<li>
<RouterLink
v-shortcut="'KeyG KeyU'"
v-shortcut="SHORTCUTS.navigation.upcoming.binding"
:to="{ name: 'tasks.range'}"
>
<span class="menu-item-icon icon">
@ -40,7 +40,7 @@
</li>
<li>
<RouterLink
v-shortcut="'KeyG KeyP'"
v-shortcut="SHORTCUTS.navigation.projects.binding"
:to="{ name: 'projects.index'}"
>
<span class="menu-item-icon icon">
@ -51,7 +51,7 @@
</li>
<li>
<RouterLink
v-shortcut="'KeyG KeyA'"
v-shortcut="SHORTCUTS.navigation.labels.binding"
:to="{ name: 'labels.index'}"
>
<span class="menu-item-icon icon">
@ -62,7 +62,7 @@
</li>
<li>
<RouterLink
v-shortcut="'KeyG KeyM'"
v-shortcut="SHORTCUTS.navigation.teams.binding"
:to="{ name: 'teams.index'}"
>
<span class="menu-item-icon icon">
@ -127,6 +127,7 @@
<script setup lang="ts">
import {computed} from 'vue'
import {SHORTCUTS} from '@/constants/shortcuts'
import PoweredByLink from '@/components/home/PoweredByLink.vue'
import Logo from '@/components/home/Logo.vue'
import Loading from '@/components/misc/Loading.vue'

View File

@ -1,8 +1,9 @@
import type {RouteLocation} from 'vue-router'
import {SHORTCUTS} from '@/constants/shortcuts'
import {isAppleDevice} from '@/helpers/isAppleDevice'
const ctrl = isAppleDevice() ? '⌘' : 'ctrl'
const ctrl = SHORTCUTS.toggleMenu.keys[0]
const reminderModifier = isAppleDevice() ? 'shift' : 'alt'
const deleteKey = isAppleDevice() ? 'backspace' : 'delete'
@ -24,11 +25,11 @@ export const KEYBOARD_SHORTCUTS: ShortcutGroup[] = [
shortcuts: [
{
title: 'keyboardShortcuts.toggleMenu',
keys: [ctrl, 'e'],
keys: SHORTCUTS.toggleMenu.keys,
},
{
title: 'keyboardShortcuts.quickSearch',
keys: [ctrl, 'k'],
keys: [SHORTCUTS.toggleMenu.keys[0], 'k'],
},
],
},
@ -37,28 +38,28 @@ export const KEYBOARD_SHORTCUTS: ShortcutGroup[] = [
shortcuts: [
{
title: 'keyboardShortcuts.navigation.overview',
keys: ['g', 'o'],
combination: 'then',
keys: SHORTCUTS.navigation.overview.keys,
combination: SHORTCUTS.navigation.overview.combination,
},
{
title: 'keyboardShortcuts.navigation.upcoming',
keys: ['g', 'u'],
combination: 'then',
keys: SHORTCUTS.navigation.upcoming.keys,
combination: SHORTCUTS.navigation.upcoming.combination,
},
{
title: 'keyboardShortcuts.navigation.projects',
keys: ['g', 'p'],
combination: 'then',
keys: SHORTCUTS.navigation.projects.keys,
combination: SHORTCUTS.navigation.projects.combination,
},
{
title: 'keyboardShortcuts.navigation.labels',
keys: ['g', 'a'],
combination: 'then',
keys: SHORTCUTS.navigation.labels.keys,
combination: SHORTCUTS.navigation.labels.combination,
},
{
title: 'keyboardShortcuts.navigation.teams',
keys: ['g', 'm'],
combination: 'then',
keys: SHORTCUTS.navigation.teams.keys,
combination: SHORTCUTS.navigation.teams.combination,
},
],
},
@ -208,7 +209,7 @@ export const KEYBOARD_SHORTCUTS: ShortcutGroup[] = [
},
{
title: 'keyboardShortcuts.task.save',
keys: [ctrl, 's'],
keys: [SHORTCUTS.toggleMenu.keys[0], 's'],
},
{
title: 'keyboardShortcuts.task.copyIdentifier',

View File

@ -0,0 +1,47 @@
import {isAppleDevice} from '@/helpers/isAppleDevice'
type ShortcutDefinition = {
binding: string
keys: string[]
combination?: 'then'
}
const ctrl = isAppleDevice() ? '⌘' : 'ctrl'
export const SHORTCUTS = {
toggleMenu: {
binding: 'Mod+KeyE',
keys: [ctrl, 'e'],
},
showKeyboardShortcuts: {
binding: 'Shift+Slash',
keys: ['shift', '/'],
},
navigation: {
overview: {
binding: 'KeyG KeyO',
keys: ['g', 'o'],
combination: 'then',
},
upcoming: {
binding: 'KeyG KeyU',
keys: ['g', 'u'],
combination: 'then',
},
projects: {
binding: 'KeyG KeyP',
keys: ['g', 'p'],
combination: 'then',
},
labels: {
binding: 'KeyG KeyA',
keys: ['g', 'a'],
combination: 'then',
},
teams: {
binding: 'KeyG KeyM',
keys: ['g', 'm'],
combination: 'then',
},
},
} as const satisfies Record<string, ShortcutDefinition | Record<string, ShortcutDefinition>>

View File

@ -16,6 +16,17 @@ test.describe('The Menu', () => {
await expect(page.locator('.menu-container')).not.toHaveClass(/is-active/)
})
test('Can be toggled with keyboard shortcut on desktop', async ({authenticatedPage: page}) => {
await expect(page.locator('.menu-container')).toHaveClass(/is-active/)
await page.locator('body').click()
await page.locator('body').press('ControlOrMeta+e')
await expect(page.locator('.menu-container')).not.toHaveClass(/is-active/)
await page.locator('body').press('ControlOrMeta+e')
await expect(page.locator('.menu-container')).toHaveClass(/is-active/)
})
test('Is hidden by default on mobile', async ({authenticatedPage: page}) => {
await page.setViewportSize(iPhone8)
await expect(page.locator('.menu-container')).not.toHaveClass(/is-active/)