diff --git a/frontend/src/components/misc/keyboard-shortcuts/shortcuts.ts b/frontend/src/components/misc/keyboard-shortcuts/shortcuts.ts index 07c75f362..5e285d102 100644 --- a/frontend/src/components/misc/keyboard-shortcuts/shortcuts.ts +++ b/frontend/src/components/misc/keyboard-shortcuts/shortcuts.ts @@ -209,6 +209,24 @@ export const KEYBOARD_SHORTCUTS: ShortcutGroup[] = [ title: 'keyboardShortcuts.task.save', keys: [ctrl, 's'], }, + { + title: 'keyboardShortcuts.task.copyIdentifier', + keys: ['.'], + }, + { + title: 'keyboardShortcuts.task.copyIdentifierAndTitle', + keys: ['.', '.'], + combination: 'then', + }, + { + title: 'keyboardShortcuts.task.copyIdentifierTitleAndUrl', + keys: ['.', '.', '.'], + combination: 'then', + }, + { + title: 'keyboardShortcuts.task.copyUrl', + keys: [ctrl, '.'], + }, ], }, ] as const diff --git a/frontend/src/composables/useTaskDetailShortcuts.ts b/frontend/src/composables/useTaskDetailShortcuts.ts new file mode 100644 index 000000000..fd63922d4 --- /dev/null +++ b/frontend/src/composables/useTaskDetailShortcuts.ts @@ -0,0 +1,100 @@ +import {ref, onMounted, onBeforeUnmount} from 'vue' +import {eventToHotkeyString} from '@github/hotkey' + +import {getTaskIdentifier} from '@/models/task' +import type {ITask} from '@/modelTypes/ITask' + +interface UseTaskDetailShortcutsOptions { + task: () => ITask + taskTitle: () => string + onSave: () => void +} + +async function copySavely(value: string) { + + try { + await navigator.clipboard.writeText(value) + } catch(e) { + console.error('could not write to clipboard', e) + } +} + +export function useTaskDetailShortcuts({ + task, + taskTitle, + onSave, +}: UseTaskDetailShortcutsOptions) { + const dotKeyPressedTimes = ref(0) + const dotKeyCopyValue = ref('') + let dotKeyPressedTimeout: ReturnType | null = null + + function resetDotKeyPressed() { + dotKeyPressedTimes.value = 0 + dotKeyCopyValue.value = '' + if (dotKeyPressedTimeout !== null) { + clearTimeout(dotKeyPressedTimeout) + dotKeyPressedTimeout = null + } + } + + // See https://github.com/github/hotkey/discussions/85#discussioncomment-5214660 + async function handleTaskHotkey(event: KeyboardEvent) { + const hotkeyString = eventToHotkeyString(event) + if (!hotkeyString) return + + if (hotkeyString === 'Control+s' || hotkeyString === 'Meta+s') { + event.preventDefault() + onSave() + return + } + + const target = event.target as HTMLElement + + if ( + target.tagName.toLowerCase() === 'input' || + target.tagName.toLowerCase() === 'textarea' || + target.contentEditable === 'true' + ) { + return + } + + if (hotkeyString === 'Control+.') { + await copySavely(window.location.href) + return + } + + if (hotkeyString === '.') { + dotKeyPressedTimes.value++ + if (dotKeyPressedTimeout !== null) { + clearTimeout(dotKeyPressedTimeout) + } + dotKeyPressedTimeout = setTimeout(async () => { + await copySavely(dotKeyCopyValue.value) + resetDotKeyPressed() + }, 300) + + switch (dotKeyPressedTimes.value) { + case 1: + dotKeyCopyValue.value = getTaskIdentifier(task()) + break + case 2: + dotKeyCopyValue.value += ' - ' + taskTitle() + break + case 3: + dotKeyCopyValue.value += ' - ' + window.location.href + break + default: + resetDotKeyPressed() + } + } + } + + onMounted(() => { + document.addEventListener('keydown', handleTaskHotkey) + }) + + onBeforeUnmount(() => { + document.removeEventListener('keydown', handleTaskHotkey) + resetDotKeyPressed() + }) +} diff --git a/frontend/src/i18n/lang/en.json b/frontend/src/i18n/lang/en.json index e917c3c9f..79ec86fcb 100644 --- a/frontend/src/i18n/lang/en.json +++ b/frontend/src/i18n/lang/en.json @@ -1117,7 +1117,11 @@ "priority": "Change the priority of this task", "favorite": "Mark this task as favorite / unfavorite", "openProject": "Open the project of this task", - "save": "Save the current task" + "save": "Save the current task", + "copyIdentifier": "Copy task identifier to clipboard", + "copyIdentifierAndTitle": "Copy task identifier and title to clipboard", + "copyIdentifierTitleAndUrl": "Copy task identifier, title and URL to clipboard", + "copyUrl": "Copy task URL to clipboard" }, "project": { "title": "Project Views", diff --git a/frontend/src/views/tasks/TaskDetailView.vue b/frontend/src/views/tasks/TaskDetailView.vue index b9c0a7d8e..bc6b66934 100644 --- a/frontend/src/views/tasks/TaskDetailView.vue +++ b/frontend/src/views/tasks/TaskDetailView.vue @@ -614,13 +614,12 @@