diff --git a/app/src/components/v-dialog/v-dialog.vue b/app/src/components/v-dialog/v-dialog.vue index d7d411cd3c..11560c4e97 100644 --- a/app/src/components/v-dialog/v-dialog.vue +++ b/app/src/components/v-dialog/v-dialog.vue @@ -34,13 +34,14 @@ export default defineComponent({ setup(props, { emit }) { const dialog = ref(null); useShortcut( - 'esc', () => { - console.log('A', dialog.value); - - emit('toggle', false); + if (_active.value) { + emitToggle(); + return true; + } }, - ref(document.body) + ref(document.body), + 'escape' ); const localActive = ref(false); diff --git a/app/src/components/v-table/v-table.vue b/app/src/components/v-table/v-table.vue index 2cd9a55efb..36d3f4f81b 100644 --- a/app/src/components/v-table/v-table.vue +++ b/app/src/components/v-table/v-table.vue @@ -295,11 +295,11 @@ export default defineComponent({ }); useShortcut( - 'mod+a', () => { onToggleSelectAll(!allItemsSelected.value); }, - table + table, + 'meta+a' ); return { diff --git a/app/src/composables/use-shortcut/readme.md b/app/src/composables/use-shortcut/readme.md index e7a79bcdd4..3b7eb3694f 100644 --- a/app/src/composables/use-shortcut/readme.md +++ b/app/src/composables/use-shortcut/readme.md @@ -17,7 +17,7 @@ import { useShortcut } from '@/composables/use-shortcut'; export default defineComponent({ setup(props) { - useShortcut('mod+s', save); + useShortcut('meta+s', save); function save() { // ... diff --git a/app/src/composables/use-shortcut/use-shortcut.ts b/app/src/composables/use-shortcut/use-shortcut.ts index de0fa96a2f..5400b4b992 100644 --- a/app/src/composables/use-shortcut/use-shortcut.ts +++ b/app/src/composables/use-shortcut/use-shortcut.ts @@ -1,19 +1,22 @@ import { onMounted, onUnmounted, Ref } from '@vue/composition-api'; import Vue from 'vue'; -type ShortcutHandler = () => void; +type ShortcutHandler = (event: KeyboardEvent) => void | any | boolean; -let keysdown: string[] = []; +const keysdown: Set = new Set([]); const handlers: Record = {}; document.body.addEventListener('keydown', (event: KeyboardEvent) => { - console.log(event.CAPTURING_PHASE); - keysdown.push(mapKeys(event.key)); - callHandlers(); + if (event.repeat) return; + + keysdown.add(mapKeys(event.key)); + callHandlers(event); }); document.body.addEventListener('keyup', (event: KeyboardEvent) => { - keysdown = keysdown.filter((key) => key === mapKeys(event.key)); + const key = mapKeys(event.key); + keysdown.delete(key.toLowerCase()); + keysdown.delete(key.toUpperCase()); }); function mapKeys(key: string) { @@ -21,31 +24,31 @@ function mapKeys(key: string) { Control: 'meta', Command: 'meta', }; - return map.hasOwnProperty(key) ? map[key] : key; -} + key = map.hasOwnProperty(key) ? map[key] : key; -function callHandlers() { - Object.entries(handlers).forEach(([key, value]) => { - const rest = key.split('+').filter((keySegment) => keysdown.includes(keySegment) === false); - // strg+A strg+shift+aaaa - - if (rest.length > 0) return; - value.forEach((f) => f()); - }); -} - -function filterShortcut(shortcut: string) { - let sections = shortcut.split('+'); - if (sections.find((s) => s.match(/^[A-Z]$/) !== null)) { - const filtered = sections.filter((s) => s.toLowerCase() === 'shift'); - if (filtered.length % 2 === 0) { - sections.unshift('shift'); - } else { - sections = sections.filter((s) => s.toLowerCase() !== 'shift'); - } - return sections.join('+').toLowerCase(); + if (key.match(/^[a-z]$/) !== null) { + if (keysdown.has('shift')) key = key.toUpperCase(); + } else if (key.match(/^[A-Z]$/) !== null) { + if (keysdown.has('shift')) key = key.toLowerCase(); + } else { + key = key.toLowerCase(); } - return shortcut.toLowerCase(); + + return key; +} + +function callHandlers(event: KeyboardEvent) { + Object.entries(handlers).forEach(([key, value]) => { + const rest = key.split('+').filter((keySegment) => keysdown.has(keySegment) === false); + if (rest.length > 0) return; + event.preventDefault(); + for (let i = 0; i < value.length; i++) { + const cancel = value[i](event); + + // if the return value is true discontinue going through the queue. + if (typeof cancel === 'boolean' && cancel === true) break; + } + }); } export default function useShortcut( @@ -53,7 +56,7 @@ export default function useShortcut( reference: Ref | Ref, ...shortcuts: string[] ) { - const callback: ShortcutHandler = () => { + const callback: ShortcutHandler = (event) => { if (reference.value === null) return; const ref = reference.value instanceof HTMLElement ? reference.value : (reference.value.$el as HTMLElement); @@ -62,24 +65,26 @@ export default function useShortcut( ref.contains(document.activeElement) || document.activeElement === document.body ) { - handler(); + return handler(event); } + return false; }; onMounted(() => { shortcuts.forEach((shortcut) => { - const s = filterShortcut(shortcut); - if (handlers.hasOwnProperty(s)) { - handlers[s].unshift(callback); + if (handlers.hasOwnProperty(shortcut)) { + handlers[shortcut].unshift(callback); } else { - handlers[s] = [callback]; + handlers[shortcut] = [callback]; } }); }); onUnmounted(() => { shortcuts.forEach((shortcut) => { - const s = filterShortcut(shortcut); - if (handlers.hasOwnProperty(s)) { - handlers[s] = handlers[s].filter((f) => f !== callback); + if (handlers.hasOwnProperty(shortcut)) { + handlers[shortcut] = handlers[shortcut].filter((f) => f !== callback); + if (handlers[shortcut].length === 0) { + delete handlers[shortcut]; + } } }); }); diff --git a/app/src/modules/collections/routes/detail.vue b/app/src/modules/collections/routes/detail.vue index 813578f675..07bdcba8ea 100644 --- a/app/src/modules/collections/routes/detail.vue +++ b/app/src/modules/collections/routes/detail.vue @@ -225,7 +225,7 @@ export default defineComponent({ }, }, setup(props) { - const form = ref(null); + const form = ref(null); const userStore = useUserStore(); const { collection, primaryKey } = toRefs(props); @@ -286,8 +286,8 @@ export default defineComponent({ return i18n.t('archive'); }); - useShortcut('mod+s', saveAndStay, form); - useShortcut('mod+shift+s', saveAndAddNew, form); + useShortcut(saveAndStay, form, 'meta+s'); + useShortcut(saveAndAddNew, form, 'meta+shift+s'); const navigationGuard: NavigationGuard = (to, from, next) => { const hasEdits = Object.keys(edits.value).length > 0; diff --git a/app/src/modules/files/routes/detail.vue b/app/src/modules/files/routes/detail.vue index f645ddfb70..c910930347 100644 --- a/app/src/modules/files/routes/detail.vue +++ b/app/src/modules/files/routes/detail.vue @@ -293,7 +293,7 @@ export default defineComponent({ const { moveToDialogActive, moveToFolder, moving, selectedFolder } = useMovetoFolder(); - useShortcut('mod+s', saveAndStay, form); + useShortcut(saveAndStay, form, 'meta+s'); return { item, diff --git a/app/src/modules/settings/routes/data-model/field-detail/field-detail.vue b/app/src/modules/settings/routes/data-model/field-detail/field-detail.vue index 52337740fa..8cfacf3a45 100644 --- a/app/src/modules/settings/routes/data-model/field-detail/field-detail.vue +++ b/app/src/modules/settings/routes/data-model/field-detail/field-detail.vue @@ -1,6 +1,7 @@