chore: Improvement is keyboard shortcuts (#10925)

This commit is contained in:
Sivin Varghese
2025-02-26 14:51:55 +05:30
committed by GitHub
parent 4d588ae618
commit 0ccbe5882c
4 changed files with 91 additions and 128 deletions

View File

@@ -127,7 +127,7 @@ export default {
const uploadRef = ref(false); const uploadRef = ref(false);
const keyboardEvents = { const keyboardEvents = {
'Alt+KeyA': { '$mod+Alt+KeyA': {
action: () => { action: () => {
// TODO: This is really hacky, we need to replace the file picker component with // TODO: This is really hacky, we need to replace the file picker component with
// a custom one, where the logic and the component markup is isolated. // a custom one, where the logic and the component markup is isolated.

View File

@@ -1,41 +1,34 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useDetectKeyboardLayout } from 'dashboard/composables/useDetectKeyboardLayout'; import { useDetectKeyboardLayout } from 'dashboard/composables/useDetectKeyboardLayout';
import { SHORTCUT_KEYS } from './constants'; import { SHORTCUT_KEYS, KEYS } from './constants';
import { import {
LAYOUT_QWERTZ, LAYOUT_QWERTZ,
keysToModifyInQWERTZ, keysToModifyInQWERTZ,
} from 'shared/helpers/KeyboardHelpers'; } from 'shared/helpers/KeyboardHelpers';
import Hotkey from 'dashboard/components/base/Hotkey.vue'; import Hotkey from 'dashboard/components/base/Hotkey.vue';
defineProps({ defineProps({ show: Boolean });
show: {
type: Boolean,
default: false,
},
});
defineEmits(['close']); defineEmits(['close']);
const { t } = useI18n(); const { t } = useI18n();
const shortcutKeys = SHORTCUT_KEYS;
const currentLayout = ref(null); const currentLayout = ref(null);
const title = item => t(`KEYBOARD_SHORTCUTS.TITLE.${item.label}`); const title = computed(
() => item => t(`KEYBOARD_SHORTCUTS.TITLE.${item.label}`)
);
// Added this function to check if the keySet needs a shift key // Added this function to check if the keySet needs a shift key
// This is used to display the shift key in the modal // This is used to display the shift key in the modal
// If the current layout is QWERTZ and the keySet contains a key that needs a shift key // If the current layout is QWERTZ and the keySet contains a key that needs a shift key
// If layout is QWERTZ then we add the Shift+keysToModify to fix an known issue // If layout is QWERTZ then we add the Shift+keysToModify to fix an known issue
// https://github.com/chatwoot/chatwoot/issues/9492 // https://github.com/chatwoot/chatwoot/issues/9492
const needsShiftKey = keySet => { const needsShiftKey = computed(
return ( () => keySet =>
currentLayout.value === LAYOUT_QWERTZ && currentLayout.value === LAYOUT_QWERTZ &&
keySet.some(key => keysToModifyInQWERTZ.has(key)) keySet.some(key => keysToModifyInQWERTZ.has(key))
); );
};
onMounted(async () => { onMounted(async () => {
currentLayout.value = await useDetectKeyboardLayout(); currentLayout.value = await useDetectKeyboardLayout();
@@ -55,80 +48,46 @@ onMounted(async () => {
</h5> </h5>
<div class="flex items-center gap-2 mb-1 ml-2"> <div class="flex items-center gap-2 mb-1 ml-2">
<Hotkey custom-class="min-h-[28px] min-w-[60px] normal-case key"> <Hotkey custom-class="min-h-[28px] min-w-[60px] normal-case key">
{{ $t('KEYBOARD_SHORTCUTS.KEYS.WINDOWS_KEY_AND_COMMAND_KEY') }} {{ KEYS.WIN }}
</Hotkey> </Hotkey>
<Hotkey custom-class="min-h-[28px] min-w-[36px] key"> <Hotkey custom-class="min-h-[28px] min-w-[36px] key">
{{ $t('KEYBOARD_SHORTCUTS.KEYS.FORWARD_SLASH_KEY') }} {{ KEYS.SLASH }}
</Hotkey> </Hotkey>
</div> </div>
</div> </div>
</div> </div>
<div class="grid grid-cols-2 px-8 pt-0 pb-8 gap-x-5 gap-y-3"> <div class="grid grid-cols-2 px-8 pt-0 pb-8 gap-x-5 gap-y-3">
<div class="flex justify-between items-center min-w-[25rem]">
<h5 class="text-sm text-slate-800 dark:text-slate-100">
{{ $t('KEYBOARD_SHORTCUTS.TITLE.OPEN_CONVERSATION') }}
</h5>
<div class="flex items-center gap-2 mb-1 ml-2">
<div class="flex gap-2">
<Hotkey custom-class="min-h-[28px] min-w-[60px] normal-case key">
{{ $t('KEYBOARD_SHORTCUTS.KEYS.ALT_OR_OPTION_KEY') }}
</Hotkey>
<Hotkey custom-class="min-h-[28px] w-9 key"> {{ 'J' }} </Hotkey>
<span
class="flex items-center text-sm font-semibold text-slate-800 dark:text-slate-100"
>
{{ $t('KEYBOARD_SHORTCUTS.KEYS.FORWARD_SLASH_KEY') }}
</span>
</div>
<Hotkey custom-class="min-h-[28px] min-w-[60px] normal-case key">
{{ $t('KEYBOARD_SHORTCUTS.KEYS.ALT_OR_OPTION_KEY') }}
</Hotkey>
<Hotkey custom-class="w-9 key"> {{ 'K' }} </Hotkey>
</div>
</div>
<div class="flex justify-between items-center min-w-[25rem]">
<h5 class="text-sm text-slate-800 dark:text-slate-100">
{{ $t('KEYBOARD_SHORTCUTS.TITLE.RESOLVE_AND_NEXT') }}
</h5>
<div class="flex items-center gap-2 mb-1 ml-2">
<Hotkey custom-class="min-h-[28px] min-w-[60px] normal-case key">
{{ $t('KEYBOARD_SHORTCUTS.KEYS.WINDOWS_KEY_AND_COMMAND_KEY') }}
</Hotkey>
<Hotkey custom-class="min-h-[28px] min-w-[60px] normal-case key">
{{ $t('KEYBOARD_SHORTCUTS.KEYS.ALT_OR_OPTION_KEY') }}
</Hotkey>
<Hotkey custom-class="w-9 key"> {{ 'E' }} </Hotkey>
</div>
</div>
<div <div
v-for="shortcutKey in shortcutKeys" v-for="shortcut in SHORTCUT_KEYS"
:key="shortcutKey.id" :key="shortcut.id"
class="flex justify-between items-center min-w-[25rem]" class="flex justify-between items-center min-w-[25rem]"
> >
<h5 class="text-sm text-slate-800 min-w-[36px] dark:text-slate-100"> <h5 class="text-sm text-slate-800 min-w-[36px] dark:text-slate-100">
{{ title(shortcutKey) }} {{ title(shortcut) }}
</h5> </h5>
<div class="flex items-center gap-2 mb-1 ml-2"> <div class="flex items-center gap-2 mb-1 ml-2">
<Hotkey <template v-if="needsShiftKey(shortcut.keySet)">
v-if="needsShiftKey(shortcutKey.keySet)" <Hotkey custom-class="min-h-[28px] min-w-[36px] key">
custom-class="min-h-[28px] min-w-[36px] key" {{ KEYS.SHIFT }}
> </Hotkey>
{{ 'Shift' }} </template>
</Hotkey>
<Hotkey <template v-for="(key, index) in shortcut.displayKeys" :key="index">
:class="{ 'min-w-[60px]': shortcutKey.firstKey !== 'Up' }" <template v-if="key !== KEYS.SLASH">
custom-class="min-h-[28px] normal-case key" <Hotkey
> custom-class="min-h-[28px] min-w-[36px] key normal-case"
{{ shortcutKey.firstKey }} >
</Hotkey> {{ key }}
<Hotkey </Hotkey>
:class="{ 'normal-case': shortcutKey.secondKey === 'Down' }" </template>
custom-class="min-h-[28px] min-w-[36px] key" <span
> v-else
{{ shortcutKey.secondKey }} class="flex items-center text-sm font-semibold text-slate-800 dark:text-slate-100"
</Hotkey> >
{{ key }}
</span>
</template>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,86 +1,95 @@
export const KEYS = {
ALT: 'Alt / ⌥',
WIN: 'Win / ⌘',
SHIFT: 'Shift',
SLASH: '/',
UP: 'Up',
DOWN: 'Down',
};
export const SHORTCUT_KEYS = [ export const SHORTCUT_KEYS = [
{ {
id: 1, id: 1,
label: 'NAVIGATE_DROPDOWN', label: 'OPEN_CONVERSATION',
firstKey: 'Up', displayKeys: [KEYS.ALT, 'J', KEYS.SLASH, KEYS.ALT, 'K'],
secondKey: 'Down', keySet: ['Alt+KeyJ', 'Alt+KeyK'],
keySet: ['ArrowUp', 'ArrowDown'],
}, },
{ {
id: 2, id: 2,
label: 'RESOLVE_CONVERSATION', label: 'RESOLVE_AND_NEXT',
firstKey: 'Alt / ⌥', displayKeys: [KEYS.WIN, KEYS.ALT, 'E'],
secondKey: 'E', keySet: ['$mod+Alt+KeyE'],
keySet: ['Alt+KeyE'],
}, },
{ {
id: 3, id: 3,
label: 'GO_TO_CONVERSATION_DASHBOARD', label: 'NAVIGATE_DROPDOWN',
firstKey: 'Alt / ⌥', displayKeys: [KEYS.UP, KEYS.DOWN],
secondKey: 'C', keySet: ['ArrowUp', 'ArrowDown'],
keySet: ['Alt+KeyC'],
}, },
{ {
id: 4, id: 4,
label: 'ADD_ATTACHMENT', label: 'RESOLVE_CONVERSATION',
firstKey: 'Alt / ⌥', displayKeys: [KEYS.ALT, 'E'],
secondKey: 'A', keySet: ['Alt+KeyE'],
keySet: ['Alt+KeyA'],
}, },
{ {
id: 5, id: 5,
label: 'GO_TO_CONTACTS_DASHBOARD', label: 'GO_TO_CONVERSATION_DASHBOARD',
firstKey: 'Alt / ⌥', displayKeys: [KEYS.ALT, 'C'],
secondKey: 'V', keySet: ['Alt+KeyC'],
keySet: ['Alt+KeyV'],
}, },
{ {
id: 6, id: 6,
label: 'TOGGLE_SIDEBAR', label: 'ADD_ATTACHMENT',
firstKey: 'Alt / ⌥', displayKeys: [KEYS.WIN, KEYS.ALT, 'A'],
secondKey: 'O', keySet: ['$mod+Alt+KeyA'],
keySet: ['Alt+KeyO'],
}, },
{ {
id: 7, id: 7,
label: 'GO_TO_REPORTS_SIDEBAR', label: 'GO_TO_CONTACTS_DASHBOARD',
firstKey: 'Alt / ⌥', displayKeys: [KEYS.ALT, 'V'],
secondKey: 'R', keySet: ['Alt+KeyV'],
keySet: ['Alt+KeyR'],
}, },
{ {
id: 8, id: 8,
label: 'MOVE_TO_NEXT_TAB', label: 'TOGGLE_SIDEBAR',
firstKey: 'Alt / ⌥', displayKeys: [KEYS.ALT, 'O'],
secondKey: 'N', keySet: ['Alt+KeyO'],
keySet: ['Alt+KeyN'],
}, },
{ {
id: 9, id: 9,
label: 'GO_TO_SETTINGS', label: 'GO_TO_REPORTS_SIDEBAR',
firstKey: 'Alt / ⌥', displayKeys: [KEYS.ALT, 'R'],
secondKey: 'S', keySet: ['Alt+KeyR'],
keySet: ['Alt+KeyS'], },
{
id: 10,
label: 'MOVE_TO_NEXT_TAB',
displayKeys: [KEYS.ALT, 'N'],
keySet: ['Alt+KeyN'],
}, },
{ {
id: 11, id: 11,
label: 'SWITCH_TO_PRIVATE_NOTE', label: 'GO_TO_SETTINGS',
firstKey: 'Alt / ⌥', displayKeys: [KEYS.ALT, 'S'],
secondKey: 'P', keySet: ['Alt+KeyS'],
keySet: ['Alt+KeyP'],
}, },
{ {
id: 12, id: 12,
label: 'SWITCH_TO_REPLY', label: 'SWITCH_TO_PRIVATE_NOTE',
firstKey: 'Alt / ⌥', displayKeys: [KEYS.ALT, 'P'],
secondKey: 'L', keySet: ['Alt+KeyP'],
keySet: ['Alt+KeyL'],
}, },
{ {
id: 13, id: 13,
label: 'SWITCH_TO_REPLY',
displayKeys: [KEYS.ALT, 'L'],
keySet: ['Alt+KeyL'],
},
{
id: 14,
label: 'TOGGLE_SNOOZE_DROPDOWN', label: 'TOGGLE_SNOOZE_DROPDOWN',
firstKey: 'Alt / ⌥', displayKeys: [KEYS.ALT, 'M'],
secondKey: 'M',
keySet: ['Alt+KeyM'], keySet: ['Alt+KeyM'],
}, },
]; ];

View File

@@ -389,11 +389,6 @@
"SWITCH_TO_PRIVATE_NOTE": "Switch to Private Note", "SWITCH_TO_PRIVATE_NOTE": "Switch to Private Note",
"SWITCH_TO_REPLY": "Switch to Reply", "SWITCH_TO_REPLY": "Switch to Reply",
"TOGGLE_SNOOZE_DROPDOWN": "Toggle snooze dropdown" "TOGGLE_SNOOZE_DROPDOWN": "Toggle snooze dropdown"
},
"KEYS": {
"WINDOWS_KEY_AND_COMMAND_KEY": "Win / ⌘",
"ALT_OR_OPTION_KEY": "Alt / ⌥",
"FORWARD_SLASH_KEY": "/"
} }
} }
} }