Skip to content

Commit

Permalink
feat: Adds keyboard shortcuts for conversation actions (#2672)
Browse files Browse the repository at this point in the history
* feat: Adds keyboard shortcuts for conversation actions

* Minor fixes

* Minor fixes

* Minor fixes and add new shortcut

* MInor fixes

* Review fixes

* Minor fixes

* Code cleanup

* Minor fixes

* Uses Alt or Option key instead of shift-key

* Review fixes

* Review fixes

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
  • Loading branch information
3 people committed Aug 9, 2021
1 parent c748269 commit c523a95
Show file tree
Hide file tree
Showing 15 changed files with 311 additions and 51 deletions.
37 changes: 35 additions & 2 deletions app/javascript/dashboard/components/ChatList.vue
Expand Up @@ -19,7 +19,7 @@
{{ $t('CHAT_LIST.LIST.404') }}
</p>

<div class="conversations-list">
<div ref="activeConversation" class="conversations-list">
<conversation-card
v-for="chat in conversationList"
:key="chat.id"
Expand Down Expand Up @@ -62,16 +62,21 @@ import ChatFilter from './widgets/conversation/ChatFilter';
import ChatTypeTabs from './widgets/ChatTypeTabs';
import ConversationCard from './widgets/conversation/ConversationCard';
import timeMixin from '../mixins/time';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import conversationMixin from '../mixins/conversations';
import wootConstants from '../constants';
import {
hasPressedAltAndJKey,
hasPressedAltAndKKey,
} from 'shared/helpers/KeyboardHelpers';
export default {
components: {
ChatTypeTabs,
ConversationCard,
ChatFilter,
},
mixins: [timeMixin, conversationMixin],
mixins: [timeMixin, conversationMixin, eventListenerMixins],
props: {
conversationInbox: {
type: [String, Number],
Expand All @@ -94,6 +99,7 @@ export default {
},
computed: {
...mapGetters({
currentChat: 'getSelectedChat',
chatLists: 'getAllConversations',
mineChatsList: 'getMineChats',
allChatList: 'getAllStatusChats',
Expand Down Expand Up @@ -188,6 +194,33 @@ export default {
});
},
methods: {
handleKeyEvents(e) {
const allConversations = this.$refs.activeConversation.querySelectorAll(
'div.conversations-list div.conversation'
);
const activeConversation = this.$refs.activeConversation.querySelector(
'div.conversations-list div.conversation.active'
);
const activeConversationIndex = [...allConversations].indexOf(
activeConversation
);
const lastConversationIndex = allConversations.length - 1;
if (hasPressedAltAndJKey(e)) {
if (activeConversationIndex === -1) {
allConversations[0].click();
}
if (activeConversationIndex >= 1) {
allConversations[activeConversationIndex - 1].click();
}
}
if (hasPressedAltAndKKey(e)) {
if (activeConversationIndex === -1) {
allConversations[lastConversationIndex].click();
} else if (activeConversationIndex < lastConversationIndex) {
allConversations[activeConversationIndex + 1].click();
}
}
},
resetAndFetchData() {
this.$store.dispatch('conversationPage/reset');
this.$store.dispatch('emptyAllConversations');
Expand Down
36 changes: 35 additions & 1 deletion app/javascript/dashboard/components/buttons/ResolveAction.vue
Expand Up @@ -35,6 +35,7 @@
</woot-button>
<woot-button
v-if="showAdditionalActions"
ref="arrowDownButton"
:color-scheme="buttonClass"
:disabled="isLoading"
icon="ion-arrow-down-b"
Expand Down Expand Up @@ -99,6 +100,12 @@
import { mapGetters } from 'vuex';
import { mixin as clickaway } from 'vue-clickaway';
import alertMixin from 'shared/mixins/alertMixin';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import {
hasPressedAltAndEKey,
hasPressedCommandPlusAltAndEKey,
hasPressedAltAndMKey,
} from 'shared/helpers/KeyboardHelpers';
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
import WootDropdownSubMenu from 'shared/components/ui/dropdown/DropdownSubMenu.vue';
Expand All @@ -118,7 +125,7 @@ export default {
WootDropdownMenu,
WootDropdownSubMenu,
},
mixins: [clickaway, alertMixin],
mixins: [clickaway, alertMixin, eventListenerMixins],
props: { conversationId: { type: [String, Number], required: true } },
data() {
return {
Expand Down Expand Up @@ -164,6 +171,33 @@ export default {
},
},
methods: {
async handleKeyEvents(e) {
const allConversations = document.querySelectorAll(
'.conversations-list .conversation'
);
if (hasPressedAltAndEKey(e)) {
const activeConversation = document.querySelector(
'div.conversations-list div.conversation.active'
);
const activeConversationIndex = [...allConversations].indexOf(
activeConversation
);
const lastConversationIndex = allConversations.length - 1;
try {
await this.toggleStatus(wootConstants.STATUS_TYPE.RESOLVED);
} catch (error) {
// error
}
if (hasPressedCommandPlusAltAndEKey(e)) {
if (activeConversationIndex < lastConversationIndex) {
allConversations[activeConversationIndex + 1].click();
} else if (allConversations.length > 1) {
allConversations[0].click();
document.querySelector('.conversations-list').scrollTop = 0;
}
}
}
},
showOpenButton() {
return this.isResolved || this.isSnoozed;
},
Expand Down
23 changes: 22 additions & 1 deletion app/javascript/dashboard/components/layout/SidebarItem.vue
Expand Up @@ -59,10 +59,17 @@
import { mapGetters } from 'vuex';
import router from '../../routes';
import {
hasPressedAltAndCKey,
hasPressedAltAndVKey,
hasPressedAltAndRKey,
hasPressedAltAndSKey,
} from 'shared/helpers/KeyboardHelpers';
import adminMixin from '../../mixins/isAdmin';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import { getInboxClassByType } from 'dashboard/helper/inbox';
export default {
mixins: [adminMixin],
mixins: [adminMixin, eventListenerMixins],
props: {
menuItem: {
type: Object,
Expand Down Expand Up @@ -117,6 +124,20 @@ export default {
}
}
},
handleKeyEvents(e) {
if (hasPressedAltAndCKey(e)) {
router.push({ name: 'home' });
}
if (hasPressedAltAndVKey(e)) {
router.push({ name: 'contacts_dashboard' });
}
if (hasPressedAltAndRKey(e)) {
router.push({ name: 'settings_account_reports' });
}
if (hasPressedAltAndSKey(e)) {
router.push({ name: 'settings_home' });
}
},
showItem(item) {
return this.isAdmin && item.newLink !== undefined;
},
Expand Down
12 changes: 12 additions & 0 deletions app/javascript/dashboard/components/widgets/ChatTypeTabs.vue
Expand Up @@ -10,8 +10,11 @@
</template>
<script>
import wootConstants from '../../constants';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import { hasPressedAltAndNKey } from 'shared/helpers/KeyboardHelpers';
export default {
mixins: [eventListenerMixins],
props: {
items: {
type: Array,
Expand All @@ -28,6 +31,15 @@ export default {
},
},
methods: {
handleKeyEvents(e) {
if (hasPressedAltAndNKey(e)) {
if (this.activeTab === wootConstants.ASSIGNEE_TYPE.ALL) {
this.onTabChange(0);
} else {
this.onTabChange(this.activeTabIndex + 1);
}
}
},
onTabChange(selectedTabIndex) {
if (this.items[selectedTabIndex].key !== this.activeTab) {
this.$emit('chatTabChange', this.items[selectedTabIndex].key);
Expand Down
Expand Up @@ -78,11 +78,17 @@

<script>
import FileUpload from 'vue-upload-component';
import {
hasPressedAltAndWKey,
hasPressedAltAndAKey,
} from 'shared/helpers/KeyboardHelpers';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import { REPLY_EDITOR_MODES } from './constants';
export default {
name: 'ReplyTopPanel',
components: { FileUpload },
mixins: [eventListenerMixins],
props: {
mode: {
type: String,
Expand Down Expand Up @@ -156,6 +162,14 @@ export default {
},
},
methods: {
handleKeyEvents(e) {
if (hasPressedAltAndWKey(e)) {
this.toggleFormatMode();
}
if (hasPressedAltAndAKey(e)) {
this.$refs.upload.$children[1].$el.click();
}
},
toggleFormatMode() {
this.setFormatMode(!this.isFormatMode);
},
Expand Down
Expand Up @@ -32,11 +32,17 @@
<script>
import { REPLY_EDITOR_MODES, CHAR_LENGTH_WARNING } from './constants';
import EmojiOrIcon from 'shared/components/EmojiOrIcon';
import {
hasPressedAltAndPKey,
hasPressedAltAndLKey,
} from 'shared/helpers/KeyboardHelpers';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
export default {
name: 'ReplyTopPanel',
components: {
EmojiOrIcon,
},
mixins: [eventListenerMixins],
props: {
mode: {
type: String,
Expand Down Expand Up @@ -76,6 +82,14 @@ export default {
},
},
methods: {
handleKeyEvents(e) {
if (hasPressedAltAndPKey(e)) {
this.handleNoteClick();
}
if (hasPressedAltAndLKey(e)) {
this.handleReplyClick();
}
},
handleReplyClick() {
this.setReplyMode(REPLY_EDITOR_MODES.REPLY);
},
Expand Down
Expand Up @@ -12,12 +12,29 @@

<script>
import wootConstants from '../../../constants';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import { hasPressedAltAndBKey } from 'shared/helpers/KeyboardHelpers';
export default {
mixins: [eventListenerMixins],
data: () => ({
activeStatus: wootConstants.STATUS_TYPE.OPEN,
}),
methods: {
handleKeyEvents(e) {
if (hasPressedAltAndBKey(e)) {
if (this.activeStatus === wootConstants.STATUS_TYPE.OPEN) {
this.activeStatus = wootConstants.STATUS_TYPE.RESOLVED;
} else if (this.activeStatus === wootConstants.STATUS_TYPE.RESOLVED) {
this.activeStatus = wootConstants.STATUS_TYPE.PENDING;
} else if (this.activeStatus === wootConstants.STATUS_TYPE.PENDING) {
this.activeStatus = wootConstants.STATUS_TYPE.SNOOZED;
} else if (this.activeStatus === wootConstants.STATUS_TYPE.SNOOZED) {
this.activeStatus = wootConstants.STATUS_TYPE.OPEN;
}
}
this.onTabChange();
},
onTabChange() {
this.$store.dispatch('setChatFilter', this.activeStatus);
this.$emit('statusFilterChange', this.activeStatus);
Expand Down
Expand Up @@ -68,15 +68,17 @@ import { mapGetters } from 'vuex';
import MoreActions from './MoreActions';
import Thumbnail from '../Thumbnail';
import agentMixin from '../../../mixins/agentMixin.js';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import AvailabilityStatusBadge from '../conversation/AvailabilityStatusBadge';
import { hasPressedAltAndOKey } from 'shared/helpers/KeyboardHelpers';
export default {
components: {
MoreActions,
Thumbnail,
AvailabilityStatusBadge,
},
mixins: [agentMixin],
mixins: [agentMixin, eventListenerMixins],
props: {
chat: {
type: Object,
Expand Down Expand Up @@ -117,6 +119,11 @@ export default {
},
methods: {
handleKeyEvents(e) {
if (hasPressedAltAndOKey(e)) {
this.$emit('contact-panel-toggle');
}
},
assignAgent(agent) {
this.$store
.dispatch('assignAgent', {
Expand Down
Expand Up @@ -74,6 +74,7 @@
import { mapGetters } from 'vuex';
import { mixin as clickaway } from 'vue-clickaway';
import alertMixin from 'shared/mixins/alertMixin';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import EmojiInput from 'shared/components/emoji/EmojiInput';
import CannedResponse from './CannedResponse';
Expand Down Expand Up @@ -105,7 +106,13 @@ export default {
ReplyBottomPanel,
WootMessageEditor,
},
mixins: [clickaway, inboxMixin, uiSettingsMixin, alertMixin],
mixins: [
clickaway,
inboxMixin,
uiSettingsMixin,
alertMixin,
eventListenerMixins,
],
props: {
selectedTweet: {
type: [Object, String],
Expand Down Expand Up @@ -289,12 +296,6 @@ export default {
}
},
},
mounted() {
document.addEventListener('keydown', this.handleKeyEvents);
},
destroyed() {
document.removeEventListener('keydown', this.handleKeyEvents);
},
methods: {
toggleUserMention(currentMentionState) {
this.hasUserMention = currentMentionState;
Expand Down Expand Up @@ -353,6 +354,9 @@ export default {
if (this.showRichContentEditor) {
return;
}
if (this.$refs.messageInput === undefined) {
return;
}
this.$nextTick(() => this.$refs.messageInput.focus());
},
emojiOnClick(emoji) {
Expand Down
Expand Up @@ -9,7 +9,11 @@
icon="ion-pricetags"
emoji="🏷️"
/>
<div v-on-clickaway="closeDropdownLabel" class="label-wrap">
<div
v-on-clickaway="closeDropdownLabel"
class="label-wrap"
@keyup.esc="closeDropdownLabel"
>
<add-label @add="toggleLabels" />
<woot-label
v-for="label in activeLabels"
Expand Down

0 comments on commit c523a95

Please sign in to comment.