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-drag-resize": "1.4.1",
|
||||
"vue-easymde": "1.2.2",
|
||||
"vue-shortkey": "^3.1.7",
|
||||
"vue-smooth-dnd": "0.8.1",
|
||||
"vuex": "3.5.1"
|
||||
},
|
||||
|
|
17
src/App.vue
17
src/App.vue
|
@ -62,6 +62,7 @@
|
|||
</router-link>
|
||||
<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 @click="keyboardShortcutsActive = true" class="dropdown-item">Keyboard Shortcuts</a>
|
||||
<a @click="logout()" class="dropdown-item">
|
||||
Logout
|
||||
</a>
|
||||
|
@ -137,7 +138,11 @@
|
|||
</li>
|
||||
</ul>
|
||||
</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">
|
||||
<div class="spinner" :class="{ 'is-loading': namespaceService.loading}"></div>
|
||||
<template v-for="n in namespaces">
|
||||
|
@ -217,6 +222,9 @@
|
|||
<transition name="fade">
|
||||
<router-view/>
|
||||
</transition>
|
||||
<a class="keyboard-shortcuts-button" @click="keyboardShortcutsActive = true">
|
||||
<icon icon="keyboard"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -270,6 +278,10 @@
|
|||
<p>Please check your network connection and try again.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<transition name="fade">
|
||||
<keyboard-shortcuts v-if="keyboardShortcutsActive" @close="keyboardShortcutsActive = false"/>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -283,10 +295,12 @@
|
|||
import swEvents from './ServiceWorker/events'
|
||||
import Notification from './components/misc/notification'
|
||||
import {CURRENT_LIST, IS_FULLPAGE, ONLINE} from './store/mutation-types'
|
||||
import KeyboardShortcuts from './components/misc/keyboard-shortcuts'
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
components: {
|
||||
KeyboardShortcuts,
|
||||
Notification,
|
||||
},
|
||||
data() {
|
||||
|
@ -296,6 +310,7 @@
|
|||
currentDate: new Date(),
|
||||
userMenuActive: false,
|
||||
authTypes: authTypes,
|
||||
keyboardShortcutsActive: false,
|
||||
|
||||
// Service Worker stuff
|
||||
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 { faFilter } 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 { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||
|
||||
|
@ -115,6 +116,7 @@ library.add(faList)
|
|||
library.add(faEllipsisV)
|
||||
library.add(faFilter)
|
||||
library.add(faFillDrip)
|
||||
library.add(faKeyboard)
|
||||
|
||||
Vue.component('icon', FontAwesomeIcon)
|
||||
|
||||
|
@ -125,6 +127,10 @@ Vue.use(VTooltip)
|
|||
// PWA
|
||||
import './registerServiceWorker'
|
||||
|
||||
// Shortcuts
|
||||
import vueShortkey from 'vue-shortkey'
|
||||
Vue.use(vueShortkey)
|
||||
|
||||
// Set focus
|
||||
Vue.directive('focus', {
|
||||
// When the bound element is inserted into the DOM...
|
||||
|
|
|
@ -20,3 +20,4 @@
|
|||
@import 'color-picker';
|
||||
@import 'namespaces';
|
||||
@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;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.has-no-shadow {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
|
|
@ -244,11 +244,11 @@
|
|||
Done!
|
||||
</template>
|
||||
</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>
|
||||
Assign this task to a user
|
||||
</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>
|
||||
Add labels
|
||||
</a>
|
||||
|
@ -256,7 +256,7 @@
|
|||
<span class="icon is-small"><icon icon="history"/></span>
|
||||
Set Reminders
|
||||
</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>
|
||||
Set Due Date
|
||||
</a>
|
||||
|
@ -280,11 +280,11 @@
|
|||
<span class="icon is-small"><icon icon="percent"/></span>
|
||||
Set Percent Done
|
||||
</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>
|
||||
Add attachments
|
||||
</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>
|
||||
Add task relations
|
||||
</a>
|
||||
|
|
18
yarn.lock
18
yarn.lock
|
@ -4705,6 +4705,11 @@ currently-unhandled@^0.4.1:
|
|||
dependencies:
|
||||
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:
|
||||
version "1.0.1"
|
||||
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"
|
||||
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:
|
||||
version "6.5.2"
|
||||
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:
|
||||
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:
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-smooth-dnd/-/vue-smooth-dnd-0.8.1.tgz#b1c584cfe49b830a402548b4bf08f00f68f430e5"
|
||||
|
|
Loading…
Reference in a new issue