From 47f8b2cd0caf75871f797003295c2459c9e21900 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 26 Apr 2024 15:41:02 +0530 Subject: [PATCH] refactor: handling keyboard shortcuts (#9242) * fix: Resolve and go next keyboard shortcuts doesn't work * refactor: use buildHotKeys instead of hasPressedCommandPlusAltAndEKey * feat: install tinykeys * refactor: use tinykeys * test: update buildKeyEvents * fix: remove stray import * feat: handle action list globally * feat: allow configuring `allowOnFocusedInput` * chore: Navigate chat list item * chore: Navigate dashboard * feat: Navigate editor top panel * feat: Toggle file upload * chore: More keyboard shortcuts * chore: Update mention selection mixin * chore: Phone input * chore: Clean up * chore: Clean up * chore: Dropdown and editor * chore: Enter key to send and clean up * chore: Rename mixin * chore: Review fixes * chore: Removed unused shortcut from modal * fix: Specs --------- Co-authored-by: iamsivin Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> --- .../dashboard/components/ChatList.vue | 61 ++++----- .../components/buttons/ResolveAction.vue | 80 ++++++------ .../dashboard/components/layout/Sidebar.vue | 59 ++++----- .../components/widgets/AIAssistanceButton.vue | 24 ++-- .../components/widgets/ChatTypeTabs.vue | 25 ++-- .../components/widgets/LabelSelector.vue | 33 +++-- .../components/widgets/WootWriter/Editor.vue | 28 +++-- .../widgets/WootWriter/FullEditor.vue | 4 +- .../widgets/WootWriter/ReplyBottomPanel.vue | 18 +-- .../widgets/WootWriter/ReplyTopPanel.vue | 30 ++--- .../widgets/conversation/ConversationCard.vue | 2 +- .../conversation/ConversationHeader.vue | 15 +-- .../widgets/conversation/MessagesView.vue | 15 +-- .../widgets/conversation/ReplyBox.vue | 56 ++++++--- .../widgets/conversation/TagAgents.vue | 7 +- .../conversation/components/GalleryView.vue | 47 +++---- .../components/widgets/forms/PhoneInput.vue | 72 ++++++----- .../widgets/mentions/MentionBox.vue | 4 +- .../mentions/mentionSelectionKeyboardMixin.js | 61 ++++++--- .../mentionSelectionKeyboardMixin.spec.js | 99 ++++++++------- .../components/widgets/modal/constants.js | 6 - .../dashboard/i18n/locale/en/settings.json | 1 - .../modules/notes/components/AddNote.vue | 25 ++-- .../conversation/labels/LabelBox.vue | 41 +++--- .../components/ArticleSearch/Header.vue | 24 ++-- .../ArticleSearch/SearchPopover.vue | 25 ++-- .../portal/components/SearchSuggestions.vue | 7 +- .../components/ui/dropdown/DropdownMenu.vue | 68 +++++----- .../shared/helpers/KeyboardHelpers.js | 117 +----------------- .../helpers/specs/KeyboardHelpers.spec.js | 13 +- .../shared/mixins/eventListenerMixins.js | 24 ---- .../mixins/keyboardEventListenerMixins.js | 63 ++++++++++ .../views/playground/Index.vue | 22 ++-- .../widget/components/Form/PhoneInput.vue | 13 +- package.json | 1 + yarn.lock | 5 + 36 files changed, 599 insertions(+), 596 deletions(-) delete mode 100644 app/javascript/shared/mixins/eventListenerMixins.js create mode 100644 app/javascript/shared/mixins/keyboardEventListenerMixins.js diff --git a/app/javascript/dashboard/components/ChatList.vue b/app/javascript/dashboard/components/ChatList.vue index 26c057f98..e7fae6e01 100644 --- a/app/javascript/dashboard/components/ChatList.vue +++ b/app/javascript/dashboard/components/ChatList.vue @@ -185,7 +185,7 @@ import ConversationBasicFilter from './widgets/conversation/ConversationBasicFil import ChatTypeTabs from './widgets/ChatTypeTabs.vue'; import ConversationItem from './ConversationItem.vue'; import timeMixin from '../mixins/time'; -import eventListenerMixins from 'shared/mixins/eventListenerMixins'; +import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins'; import conversationMixin from '../mixins/conversations'; import wootConstants from 'dashboard/constants/globals'; import advancedFilterTypes from './widgets/conversation/advancedFilterItems'; @@ -199,11 +199,6 @@ import uiSettingsMixin from 'dashboard/mixins/uiSettings'; import languages from 'dashboard/components/widgets/conversation/advancedFilterItems/languages'; import countries from 'shared/constants/countries'; import { generateValuesForEditCustomViews } from 'dashboard/helper/customViewsHelper'; - -import { - hasPressedAltAndJKey, - hasPressedAltAndKKey, -} from 'shared/helpers/KeyboardHelpers'; import { conversationListPageURL } from '../helper/URLHelper'; import { isOnMentionsView, @@ -228,7 +223,7 @@ export default { mixins: [ timeMixin, conversationMixin, - eventListenerMixins, + keyboardEventListenerMixins, alertMixin, filterMixin, uiSettingsMixin, @@ -691,30 +686,40 @@ export default { lastConversationIndex, }; }, - handleKeyEvents(e) { - if (hasPressedAltAndJKey(e)) { - const { allConversations, activeConversationIndex } = - this.getKeyboardListenerParams(); - if (activeConversationIndex === -1) { - allConversations[0].click(); - } - if (activeConversationIndex >= 1) { - allConversations[activeConversationIndex - 1].click(); - } + handlePreviousConversation() { + const { allConversations, activeConversationIndex } = + this.getKeyboardListenerParams(); + if (activeConversationIndex === -1) { + allConversations[0].click(); } - if (hasPressedAltAndKKey(e)) { - const { - allConversations, - activeConversationIndex, - lastConversationIndex, - } = this.getKeyboardListenerParams(); - if (activeConversationIndex === -1) { - allConversations[lastConversationIndex].click(); - } else if (activeConversationIndex < lastConversationIndex) { - allConversations[activeConversationIndex + 1].click(); - } + if (activeConversationIndex >= 1) { + allConversations[activeConversationIndex - 1].click(); } }, + handleNextConversation() { + const { + allConversations, + activeConversationIndex, + lastConversationIndex, + } = this.getKeyboardListenerParams(); + if (activeConversationIndex === -1) { + allConversations[lastConversationIndex].click(); + } else if (activeConversationIndex < lastConversationIndex) { + allConversations[activeConversationIndex + 1].click(); + } + }, + getKeyboardEvents() { + return { + 'Alt+KeyJ': { + action: () => this.handlePreviousConversation(), + allowOnFocusedInput: true, + }, + 'Alt+KeyK': { + action: () => this.handleNextConversation(), + allowOnFocusedInput: true, + }, + }; + }, resetAndFetchData() { this.appliedFilter = []; this.resetBulkActions(); diff --git a/app/javascript/dashboard/components/buttons/ResolveAction.vue b/app/javascript/dashboard/components/buttons/ResolveAction.vue index 84e33cc42..9a70a7f19 100644 --- a/app/javascript/dashboard/components/buttons/ResolveAction.vue +++ b/app/javascript/dashboard/components/buttons/ResolveAction.vue @@ -91,12 +91,7 @@ import { mapGetters } from 'vuex'; import { mixin as clickaway } from 'vue-clickaway'; import alertMixin from 'shared/mixins/alertMixin'; import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal.vue'; -import eventListenerMixins from 'shared/mixins/eventListenerMixins'; -import { - hasPressedAltAndEKey, - hasPressedCommandPlusAltAndEKey, - hasPressedAltAndMKey, -} from 'shared/helpers/KeyboardHelpers'; +import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins'; import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers'; import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue'; import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue'; @@ -114,7 +109,7 @@ export default { WootDropdownMenu, CustomSnoozeModal, }, - mixins: [clickaway, alertMixin, eventListenerMixins], + mixins: [clickaway, alertMixin, keyboardEventListenerMixins], props: { conversationId: { type: [String, Number], required: true } }, data() { return { @@ -159,37 +154,52 @@ export default { bus.$off(CMD_RESOLVE_CONVERSATION, this.onCmdResolveConversation); }, methods: { - async handleKeyEvents(e) { + getKeyboardEvents() { + return { + 'Alt+KeyM': { + action: () => this.$refs.arrowDownButton?.$el.click(), + allowOnFocusedInput: true, + }, + 'Alt+KeyE': this.resolveOrToast, + '$mod+Alt+KeyE': async event => { + const { all, activeIndex, lastIndex } = this.getConversationParams(); + await this.resolveOrToast(); + + if (activeIndex < lastIndex) { + all[activeIndex + 1].click(); + } else if (all.length > 1) { + all[0].click(); + document.querySelector('.conversations-list').scrollTop = 0; + } + + event.preventDefault(); + }, + }; + }, + getConversationParams() { const allConversations = document.querySelectorAll( '.conversations-list .conversation' ); - if (hasPressedAltAndMKey(e)) { - if (this.$refs.arrowDownButton) { - this.$refs.arrowDownButton.$el.click(); - } - } - 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; - } - e.preventDefault(); - } + + const activeConversation = document.querySelector( + 'div.conversations-list div.conversation.active' + ); + const activeConversationIndex = [...allConversations].indexOf( + activeConversation + ); + const lastConversationIndex = allConversations.length - 1; + + return { + all: allConversations, + activeIndex: activeConversationIndex, + lastIndex: lastConversationIndex, + }; + }, + async resolveOrToast() { + try { + await this.toggleStatus(wootConstants.STATUS_TYPE.RESOLVED); + } catch (error) { + // error } }, onCmdSnoozeConversation(snoozeType) { diff --git a/app/javascript/dashboard/components/layout/Sidebar.vue b/app/javascript/dashboard/components/layout/Sidebar.vue index 1290ea602..0f3a1a857 100644 --- a/app/javascript/dashboard/components/layout/Sidebar.vue +++ b/app/javascript/dashboard/components/layout/Sidebar.vue @@ -1,5 +1,5 @@