Keyboard Shortcuts (#193)
Add the keyboard shortcuts button Add task keyboard shortcuts Add info Move keyboard shortcuts modal toggle to menu Add modal for shortcuts Add shortkeys for some task actions Add shortkey to toggle menu Co-authored-by: kolaente <k@knt.li> Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/193
This commit is contained in:
parent
5521ba7c71
commit
be093e3779
9 changed files with 194 additions and 6 deletions
|
@ -22,6 +22,7 @@
|
||||||
"vue": "2.6.11",
|
"vue": "2.6.11",
|
||||||
"vue-drag-resize": "1.4.1",
|
"vue-drag-resize": "1.4.1",
|
||||||
"vue-easymde": "1.2.2",
|
"vue-easymde": "1.2.2",
|
||||||
|
"vue-shortkey": "^3.1.7",
|
||||||
"vue-smooth-dnd": "0.8.1",
|
"vue-smooth-dnd": "0.8.1",
|
||||||
"vuex": "3.5.1"
|
"vuex": "3.5.1"
|
||||||
},
|
},
|
||||||
|
|
17
src/App.vue
17
src/App.vue
|
@ -62,6 +62,7 @@
|
||||||
</router-link>
|
</router-link>
|
||||||
<a :href="imprintUrl" v-if="imprintUrl" class="dropdown-item" target="_blank">Imprint</a>
|
<a :href="imprintUrl" v-if="imprintUrl" class="dropdown-item" target="_blank">Imprint</a>
|
||||||
<a :href="privacyPolicyUrl" v-if="privacyPolicyUrl" class="dropdown-item" target="_blank">Privacy policy</a>
|
<a :href="privacyPolicyUrl" v-if="privacyPolicyUrl" class="dropdown-item" target="_blank">Privacy policy</a>
|
||||||
|
<a @click="keyboardShortcutsActive = true" class="dropdown-item">Keyboard Shortcuts</a>
|
||||||
<a @click="logout()" class="dropdown-item">
|
<a @click="logout()" class="dropdown-item">
|
||||||
Logout
|
Logout
|
||||||
</a>
|
</a>
|
||||||
|
@ -137,7 +138,11 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<a @click="menuActive = false" class="collapse-menu-button">Collapse Menu</a>
|
|
||||||
|
<a @click="menuActive = false" class="collapse-menu-button" v-shortkey="['ctrl', 'e']" @shortkey="() => menuActive = !menuActive">
|
||||||
|
Collapse Menu
|
||||||
|
</a>
|
||||||
|
|
||||||
<aside class="menu namespaces-lists">
|
<aside class="menu namespaces-lists">
|
||||||
<div class="spinner" :class="{ 'is-loading': namespaceService.loading}"></div>
|
<div class="spinner" :class="{ 'is-loading': namespaceService.loading}"></div>
|
||||||
<template v-for="n in namespaces">
|
<template v-for="n in namespaces">
|
||||||
|
@ -217,6 +222,9 @@
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<router-view/>
|
<router-view/>
|
||||||
</transition>
|
</transition>
|
||||||
|
<a class="keyboard-shortcuts-button" @click="keyboardShortcutsActive = true">
|
||||||
|
<icon icon="keyboard"/>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -270,6 +278,10 @@
|
||||||
<p>Please check your network connection and try again.</p>
|
<p>Please check your network connection and try again.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<transition name="fade">
|
||||||
|
<keyboard-shortcuts v-if="keyboardShortcutsActive" @close="keyboardShortcutsActive = false"/>
|
||||||
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -283,10 +295,12 @@
|
||||||
import swEvents from './ServiceWorker/events'
|
import swEvents from './ServiceWorker/events'
|
||||||
import Notification from './components/misc/notification'
|
import Notification from './components/misc/notification'
|
||||||
import {CURRENT_LIST, IS_FULLPAGE, ONLINE} from './store/mutation-types'
|
import {CURRENT_LIST, IS_FULLPAGE, ONLINE} from './store/mutation-types'
|
||||||
|
import KeyboardShortcuts from './components/misc/keyboard-shortcuts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
components: {
|
components: {
|
||||||
|
KeyboardShortcuts,
|
||||||
Notification,
|
Notification,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -296,6 +310,7 @@
|
||||||
currentDate: new Date(),
|
currentDate: new Date(),
|
||||||
userMenuActive: false,
|
userMenuActive: false,
|
||||||
authTypes: authTypes,
|
authTypes: authTypes,
|
||||||
|
keyboardShortcutsActive: false,
|
||||||
|
|
||||||
// Service Worker stuff
|
// Service Worker stuff
|
||||||
updateAvailable: false,
|
updateAvailable: false,
|
||||||
|
|
99
src/components/misc/keyboard-shortcuts.vue
Normal file
99
src/components/misc/keyboard-shortcuts.vue
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
<template>
|
||||||
|
<div class="modal-mask keyboard-shortcuts-modal">
|
||||||
|
<div class="modal-container" @click.self="close()">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="card has-background-white has-no-shadow">
|
||||||
|
<header class="card-header">
|
||||||
|
<p class="card-header-title">Available Keyboard Shortcuts</p>
|
||||||
|
</header>
|
||||||
|
<div class="card-content content">
|
||||||
|
<p class="info">
|
||||||
|
The available keyboard shortcuts depend on the current page. Not all shortcuts are available
|
||||||
|
everywhere.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Toggle The Menu</strong>
|
||||||
|
<span class="shortcuts">
|
||||||
|
<span>ctrl</span>
|
||||||
|
<i>+</i>
|
||||||
|
<span>e</span>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p v-if="$route.name === 'list.kanban'">
|
||||||
|
<strong>Mark a task as done</strong>
|
||||||
|
<span class="shortcuts">
|
||||||
|
<span>ctrl</span>
|
||||||
|
<i>+</i>
|
||||||
|
<span>click</span>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<template
|
||||||
|
v-if="$route.name === 'task.detail' || $route.name === 'task.list.detail' || $route.name === 'task.gantt.detail' || $route.name === 'task.kanban.detail' || $route.name === 'task.detail'">
|
||||||
|
<p>
|
||||||
|
<strong>Assign this task to a user</strong>
|
||||||
|
<span class="shortcuts">
|
||||||
|
<span>ctrl</span>
|
||||||
|
<i>+</i>
|
||||||
|
<span>shift</span>
|
||||||
|
<i>+</i>
|
||||||
|
<span>a</span>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Add labels to this task</strong>
|
||||||
|
<span class="shortcuts">
|
||||||
|
<span>ctrl</span>
|
||||||
|
<i>+</i>
|
||||||
|
<span>shift</span>
|
||||||
|
<i>+</i>
|
||||||
|
<span>l</span>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Change the due date of this task</strong>
|
||||||
|
<span class="shortcuts">
|
||||||
|
<span>ctrl</span>
|
||||||
|
<i>+</i>
|
||||||
|
<span>shift</span>
|
||||||
|
<i>+</i>
|
||||||
|
<span>d</span>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Add an attachment to this task</strong>
|
||||||
|
<span class="shortcuts">
|
||||||
|
<span>ctrl</span>
|
||||||
|
<i>+</i>
|
||||||
|
<span>shift</span>
|
||||||
|
<i>+</i>
|
||||||
|
<span>f</span>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Modify related tasks of this task</strong>
|
||||||
|
<span class="shortcuts">
|
||||||
|
<span>ctrl</span>
|
||||||
|
<i>+</i>
|
||||||
|
<span>shift</span>
|
||||||
|
<i>+</i>
|
||||||
|
<span>r</span>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'keyboard-shortcuts',
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -68,6 +68,7 @@ import { faList } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { faEllipsisV } from '@fortawesome/free-solid-svg-icons'
|
import { faEllipsisV } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { faFilter } from '@fortawesome/free-solid-svg-icons'
|
import { faFilter } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { faFillDrip } from '@fortawesome/free-solid-svg-icons'
|
import { faFillDrip } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { faKeyboard } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { faComments } from '@fortawesome/free-regular-svg-icons'
|
import { faComments } from '@fortawesome/free-regular-svg-icons'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||||
|
|
||||||
|
@ -115,6 +116,7 @@ library.add(faList)
|
||||||
library.add(faEllipsisV)
|
library.add(faEllipsisV)
|
||||||
library.add(faFilter)
|
library.add(faFilter)
|
||||||
library.add(faFillDrip)
|
library.add(faFillDrip)
|
||||||
|
library.add(faKeyboard)
|
||||||
|
|
||||||
Vue.component('icon', FontAwesomeIcon)
|
Vue.component('icon', FontAwesomeIcon)
|
||||||
|
|
||||||
|
@ -125,6 +127,10 @@ Vue.use(VTooltip)
|
||||||
// PWA
|
// PWA
|
||||||
import './registerServiceWorker'
|
import './registerServiceWorker'
|
||||||
|
|
||||||
|
// Shortcuts
|
||||||
|
import vueShortkey from 'vue-shortkey'
|
||||||
|
Vue.use(vueShortkey)
|
||||||
|
|
||||||
// Set focus
|
// Set focus
|
||||||
Vue.directive('focus', {
|
Vue.directive('focus', {
|
||||||
// When the bound element is inserted into the DOM...
|
// When the bound element is inserted into the DOM...
|
||||||
|
|
|
@ -20,3 +20,4 @@
|
||||||
@import 'color-picker';
|
@import 'color-picker';
|
||||||
@import 'namespaces';
|
@import 'namespaces';
|
||||||
@import 'legal';
|
@import 'legal';
|
||||||
|
@import 'keyboard-shortcuts';
|
||||||
|
|
44
src/styles/components/keyboard-shortcuts.scss
Normal file
44
src/styles/components/keyboard-shortcuts.scss
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
.keyboard-shortcuts-button {
|
||||||
|
position: fixed;
|
||||||
|
bottom: calc(1rem - 4px);
|
||||||
|
right: 1rem;
|
||||||
|
z-index: 4500; // The modal has a z-index of 4000
|
||||||
|
|
||||||
|
color: $grey;
|
||||||
|
transition: color $transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyboard-shortcuts-modal {
|
||||||
|
z-index: 4600;
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
.info {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.shortcuts {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
i {
|
||||||
|
padding: 0 .25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
padding: .1rem .35rem;
|
||||||
|
border: 1px solid $grey-light;
|
||||||
|
background: lighten($grey-lightest, 2.5);
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: .75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -71,3 +71,7 @@ button.table {
|
||||||
max-width: $desktop;
|
max-width: $desktop;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.has-no-shadow {
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
|
@ -244,11 +244,11 @@
|
||||||
Done!
|
Done!
|
||||||
</template>
|
</template>
|
||||||
</a>
|
</a>
|
||||||
<a class="button" @click="setFieldActive('assignees')">
|
<a class="button" @click="setFieldActive('assignees')" v-shortkey="['ctrl', 'shift', 'a']" @shortkey="setFieldActive('assignees')">
|
||||||
<span class="icon is-small"><icon icon="users"/></span>
|
<span class="icon is-small"><icon icon="users"/></span>
|
||||||
Assign this task to a user
|
Assign this task to a user
|
||||||
</a>
|
</a>
|
||||||
<a class="button" @click="setFieldActive('labels')">
|
<a class="button" @click="setFieldActive('labels')" v-shortkey="['ctrl', 'shift', 'l']" @shortkey="setFieldActive('labels')">
|
||||||
<span class="icon is-small"><icon icon="tags"/></span>
|
<span class="icon is-small"><icon icon="tags"/></span>
|
||||||
Add labels
|
Add labels
|
||||||
</a>
|
</a>
|
||||||
|
@ -256,7 +256,7 @@
|
||||||
<span class="icon is-small"><icon icon="history"/></span>
|
<span class="icon is-small"><icon icon="history"/></span>
|
||||||
Set Reminders
|
Set Reminders
|
||||||
</a>
|
</a>
|
||||||
<a class="button" @click="setFieldActive('dueDate')">
|
<a class="button" @click="setFieldActive('dueDate')" v-shortkey="['ctrl', 'shift', 'd']" @shortkey="setFieldActive('dueDate')">
|
||||||
<span class="icon is-small"><icon icon="calendar"/></span>
|
<span class="icon is-small"><icon icon="calendar"/></span>
|
||||||
Set Due Date
|
Set Due Date
|
||||||
</a>
|
</a>
|
||||||
|
@ -280,11 +280,11 @@
|
||||||
<span class="icon is-small"><icon icon="percent"/></span>
|
<span class="icon is-small"><icon icon="percent"/></span>
|
||||||
Set Percent Done
|
Set Percent Done
|
||||||
</a>
|
</a>
|
||||||
<a class="button" @click="setFieldActive('attachments')">
|
<a class="button" @click="setFieldActive('attachments')" v-shortkey="['ctrl', 'shift', 'f']" @shortkey="setFieldActive('attachments')">
|
||||||
<span class="icon is-small"><icon icon="paperclip"/></span>
|
<span class="icon is-small"><icon icon="paperclip"/></span>
|
||||||
Add attachments
|
Add attachments
|
||||||
</a>
|
</a>
|
||||||
<a class="button" @click="setFieldActive('relatedTasks')">
|
<a class="button" @click="setFieldActive('relatedTasks')" v-shortkey="['ctrl', 'shift', 'r']" @shortkey="setFieldActive('relatedTasks')">
|
||||||
<span class="icon is-small"><icon icon="tasks"/></span>
|
<span class="icon is-small"><icon icon="tasks"/></span>
|
||||||
Add task relations
|
Add task relations
|
||||||
</a>
|
</a>
|
||||||
|
|
18
yarn.lock
18
yarn.lock
|
@ -4705,6 +4705,11 @@ currently-unhandled@^0.4.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
array-find-index "^1.0.1"
|
array-find-index "^1.0.1"
|
||||||
|
|
||||||
|
custom-event-polyfill@^1.0.7:
|
||||||
|
version "1.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz#9bc993ddda937c1a30ccd335614c6c58c4f87aee"
|
||||||
|
integrity sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==
|
||||||
|
|
||||||
cyclist@^1.0.1:
|
cyclist@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
||||||
|
@ -5226,6 +5231,11 @@ elegant-spinner@^1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
|
resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
|
||||||
integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=
|
integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=
|
||||||
|
|
||||||
|
element-matches@^0.1.2:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/element-matches/-/element-matches-0.1.2.tgz#7345cb71e965bd2b12f725e524591c102198361a"
|
||||||
|
integrity sha512-yWh1otcs3OKUWDvu/IxyI36ZI3WNaRZlI0uG/DK6fu0pap0VYZ0J5pEGTk1zakme+hT0OKHwhlHc0N5TJhY6yQ==
|
||||||
|
|
||||||
elliptic@^6.0.0:
|
elliptic@^6.0.0:
|
||||||
version "6.5.2"
|
version "6.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762"
|
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762"
|
||||||
|
@ -12563,6 +12573,14 @@ vue-sfc-descriptor-to-string@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
indent-string "^3.2.0"
|
indent-string "^3.2.0"
|
||||||
|
|
||||||
|
vue-shortkey@^3.1.7:
|
||||||
|
version "3.1.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/vue-shortkey/-/vue-shortkey-3.1.7.tgz#31c09a99ed597331a6a49a45eeff894a78a19eef"
|
||||||
|
integrity sha512-Wm/vPXXS+4Wl/LoYpD+cZc0J0HIoVlY8Ep0JLIqqswmAya3XUBtsqKbhzEf9sXo+3rZ5p1YsUyZfXas8XD7YjQ==
|
||||||
|
dependencies:
|
||||||
|
custom-event-polyfill "^1.0.7"
|
||||||
|
element-matches "^0.1.2"
|
||||||
|
|
||||||
vue-smooth-dnd@0.8.1:
|
vue-smooth-dnd@0.8.1:
|
||||||
version "0.8.1"
|
version "0.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/vue-smooth-dnd/-/vue-smooth-dnd-0.8.1.tgz#b1c584cfe49b830a402548b4bf08f00f68f430e5"
|
resolved "https://registry.yarnpkg.com/vue-smooth-dnd/-/vue-smooth-dnd-0.8.1.tgz#b1c584cfe49b830a402548b4bf08f00f68f430e5"
|
||||||
|
|
Loading…
Reference in a new issue