fix: Right click Snooze is not working (#9498)

This commit is contained in:
Sivin Varghese
2024-05-22 13:03:49 +05:30
committed by GitHub
parent db13049e6f
commit 0d13c11c44
21 changed files with 412 additions and 278 deletions

View File

@@ -7,79 +7,17 @@
]" ]"
> >
<slot /> <slot />
<div <chat-list-header
class="flex items-center justify-between px-4 py-0" :page-title="pageTitle"
:class="{ :has-applied-filters="hasAppliedFilters"
'pb-3 border-b border-slate-75 dark:border-slate-700': :has-active-folders="hasActiveFolders"
hasAppliedFiltersOrActiveFolders, :active-status="activeStatus"
}" @add-folders="onClickOpenAddFoldersModal"
> @delete-folders="onClickOpenDeleteFoldersModal"
<div class="flex max-w-[85%] justify-center items-center"> @filters-modal="onToggleAdvanceFiltersModal"
<h1 @reset-filters="resetAndFetchData"
class="text-xl font-medium break-words truncate text-black-900 dark:text-slate-100" @basic-filter-change="onBasicFilterChange"
:title="pageTitle"
>
{{ pageTitle }}
</h1>
<span
v-if="!hasAppliedFiltersOrActiveFolders"
class="p-1 my-0.5 mx-1 rounded-md capitalize bg-slate-50 dark:bg-slate-800 text-xxs text-slate-600 dark:text-slate-300"
>
{{ $t(`CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.${activeStatus}.TEXT`) }}
</span>
</div>
<div class="flex items-center gap-1">
<div v-if="hasAppliedFilters && !hasActiveFolders">
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.ADD.SAVE_BUTTON')"
size="tiny"
variant="smooth"
color-scheme="secondary"
icon="save"
@click="onClickOpenAddFoldersModal"
/> />
<woot-button
v-tooltip.top-end="$t('FILTER.CLEAR_BUTTON_LABEL')"
size="tiny"
variant="smooth"
color-scheme="alert"
icon="dismiss-circle"
@click="resetAndFetchData"
/>
</div>
<div v-if="hasActiveFolders">
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.EDIT.EDIT_BUTTON')"
size="tiny"
variant="smooth"
color-scheme="secondary"
icon="edit"
@click="onToggleAdvanceFiltersModal"
/>
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.DELETE.DELETE_BUTTON')"
size="tiny"
variant="smooth"
color-scheme="alert"
icon="delete"
@click="onClickOpenDeleteFoldersModal"
/>
</div>
<woot-button
v-else
v-tooltip.right="$t('FILTER.TOOLTIP_LABEL')"
variant="smooth"
color-scheme="secondary"
icon="filter"
size="tiny"
@click="onToggleAdvanceFiltersModal"
/>
<conversation-basic-filter
v-if="!hasAppliedFiltersOrActiveFolders"
@changeFilter="onBasicFilterChange"
/>
</div>
</div>
<add-custom-views <add-custom-views
v-if="showAddFoldersModal" v-if="showAddFoldersModal"
@@ -173,6 +111,15 @@
@updateFolder="onUpdateSavedFilter" @updateFolder="onUpdateSavedFilter"
/> />
</woot-modal> </woot-modal>
<woot-modal
:show.sync="showCustomSnoozeModal"
:on-close="hideCustomSnoozeModal"
>
<custom-snooze-modal
@close="hideCustomSnoozeModal"
@choose-time="chooseSnoozeTime"
/>
</woot-modal>
</div> </div>
</template> </template>
@@ -180,8 +127,8 @@
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import VirtualList from 'vue-virtual-scroll-list'; import VirtualList from 'vue-virtual-scroll-list';
import ChatListHeader from './ChatListHeader.vue';
import ConversationAdvancedFilter from './widgets/conversation/ConversationAdvancedFilter.vue'; import ConversationAdvancedFilter from './widgets/conversation/ConversationAdvancedFilter.vue';
import ConversationBasicFilter from './widgets/conversation/ConversationBasicFilter.vue';
import ChatTypeTabs from './widgets/ChatTypeTabs.vue'; import ChatTypeTabs from './widgets/ChatTypeTabs.vue';
import ConversationItem from './ConversationItem.vue'; import ConversationItem from './ConversationItem.vue';
import timeMixin from '../mixins/time'; import timeMixin from '../mixins/time';
@@ -205,10 +152,15 @@ import {
isOnUnattendedView, isOnUnattendedView,
} from '../store/modules/conversations/helpers/actionHelpers'; } from '../store/modules/conversations/helpers/actionHelpers';
import { CONVERSATION_EVENTS } from '../helper/AnalyticsHelper/events'; import { CONVERSATION_EVENTS } from '../helper/AnalyticsHelper/events';
import { CMD_SNOOZE_CONVERSATION } from 'dashboard/routes/dashboard/commands/commandBarBusEvents';
import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers';
import { getUnixTime } from 'date-fns';
import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal.vue';
import IntersectionObserver from './IntersectionObserver.vue'; import IntersectionObserver from './IntersectionObserver.vue';
export default { export default {
components: { components: {
ChatListHeader,
AddCustomViews, AddCustomViews,
ChatTypeTabs, ChatTypeTabs,
// eslint-disable-next-line vue/no-unused-components // eslint-disable-next-line vue/no-unused-components
@@ -216,9 +168,9 @@ export default {
ConversationAdvancedFilter, ConversationAdvancedFilter,
DeleteCustomViews, DeleteCustomViews,
ConversationBulkActions, ConversationBulkActions,
ConversationBasicFilter,
IntersectionObserver, IntersectionObserver,
VirtualList, VirtualList,
CustomSnoozeModal,
}, },
mixins: [ mixins: [
timeMixin, timeMixin,
@@ -295,6 +247,7 @@ export default {
root: this.$refs.conversationList, root: this.$refs.conversationList,
rootMargin: '100px 0px 100px 0px', rootMargin: '100px 0px 100px 0px',
}, },
showCustomSnoozeModal: false,
itemComponent: ConversationItem, itemComponent: ConversationItem,
// virtualListExtraProps is to pass the props to the conversationItem component. // virtualListExtraProps is to pass the props to the conversationItem component.
@@ -329,12 +282,13 @@ export default {
campaigns: 'campaigns/getAllCampaigns', campaigns: 'campaigns/getAllCampaigns',
labels: 'labels/getLabels', labels: 'labels/getLabels',
selectedConversations: 'bulkActions/getSelectedConversationIds', selectedConversations: 'bulkActions/getSelectedConversationIds',
contextMenuChatId: 'getContextMenuChatId',
}), }),
hasAppliedFilters() { hasAppliedFilters() {
return this.appliedFilters.length !== 0; return this.appliedFilters.length !== 0;
}, },
hasActiveFolders() { hasActiveFolders() {
return this.activeFolder && this.foldersId !== 0; return Boolean(this.activeFolder && this.foldersId !== 0);
}, },
hasAppliedFiltersOrActiveFolders() { hasAppliedFiltersOrActiveFolders() {
return this.hasAppliedFilters || this.hasActiveFolders; return this.hasAppliedFilters || this.hasActiveFolders;
@@ -558,6 +512,11 @@ export default {
bus.$on('fetch_conversation_stats', () => { bus.$on('fetch_conversation_stats', () => {
this.$store.dispatch('conversationStats/get', this.conversationFilters); this.$store.dispatch('conversationStats/get', this.conversationFilters);
}); });
bus.$on(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation);
},
beforeDestroy() {
bus.$off(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation);
}, },
methods: { methods: {
updateVirtualListProps(key, value) { updateVirtualListProps(key, value) {
@@ -1034,6 +993,43 @@ export default {
onContextMenuToggle(state) { onContextMenuToggle(state) {
this.isContextMenuOpen = state; this.isContextMenuOpen = state;
}, },
onCmdSnoozeConversation(snoozeType) {
if (snoozeType === wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME) {
this.showCustomSnoozeModal = true;
} else {
this.toggleStatus(
wootConstants.STATUS_TYPE.SNOOZED,
findSnoozeTime(snoozeType) || null
);
}
},
chooseSnoozeTime(customSnoozeTime) {
this.showCustomSnoozeModal = false;
if (customSnoozeTime) {
this.toggleStatus(
wootConstants.STATUS_TYPE.SNOOZED,
getUnixTime(customSnoozeTime)
);
}
},
toggleStatus(status, snoozedUntil) {
this.$store
.dispatch('toggleStatus', {
conversationId: this.currentChat?.id || this.contextMenuChatId,
status,
snoozedUntil,
})
.then(() => {
this.$store.dispatch('setContextMenuChatId', null);
this.showAlert(this.$t('CONVERSATION.CHANGE_STATUS'));
});
},
hideCustomSnoozeModal() {
// if we select custom snooze and then the custom snooze modal is open
// Then if the custom snooze modal is closed and set the context menu chat id to null
this.$store.dispatch('setContextMenuChatId', null);
this.showCustomSnoozeModal = false;
},
}, },
}; };
</script> </script>

View File

@@ -0,0 +1,115 @@
<script setup>
import { computed } from 'vue';
import ConversationBasicFilter from './widgets/conversation/ConversationBasicFilter.vue';
const props = defineProps({
pageTitle: {
type: String,
required: true,
},
hasAppliedFilters: {
type: Boolean,
required: true,
},
hasActiveFolders: {
type: Boolean,
required: true,
},
activeStatus: {
type: String,
required: true,
},
});
const emits = defineEmits([
'add-folders',
'delete-folders',
'reset-filters',
'basic-filter-change',
'filters-modal',
]);
const onBasicFilterChange = (value, type) => {
emits('basic-filter-change', value, type);
};
const hasAppliedFiltersOrActiveFolders = computed(() => {
return props.hasAppliedFilters || props.hasActiveFolders;
});
</script>
<template>
<div
class="flex items-center justify-between px-4 py-0"
:class="{
'pb-3 border-b border-slate-75 dark:border-slate-700':
hasAppliedFiltersOrActiveFolders,
}"
>
<div class="flex max-w-[85%] justify-center items-center">
<h1
class="text-xl font-medium break-words truncate text-black-900 dark:text-slate-100"
:title="pageTitle"
>
{{ pageTitle }}
</h1>
<span
v-if="!hasAppliedFiltersOrActiveFolders"
class="p-1 my-0.5 mx-1 rounded-md capitalize bg-slate-50 dark:bg-slate-800 text-xxs text-slate-600 dark:text-slate-300"
>
{{ $t(`CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.${activeStatus}.TEXT`) }}
</span>
</div>
<div class="flex items-center gap-1">
<div v-if="hasAppliedFilters && !hasActiveFolders">
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.ADD.SAVE_BUTTON')"
size="tiny"
variant="smooth"
color-scheme="secondary"
icon="save"
@click="emits('add-folders')"
/>
<woot-button
v-tooltip.top-end="$t('FILTER.CLEAR_BUTTON_LABEL')"
size="tiny"
variant="smooth"
color-scheme="alert"
icon="dismiss-circle"
@click="emits('reset-filters')"
/>
</div>
<div v-if="hasActiveFolders">
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.EDIT.EDIT_BUTTON')"
size="tiny"
variant="smooth"
color-scheme="secondary"
icon="edit"
@click="emits('filters-modal')"
/>
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.DELETE.DELETE_BUTTON')"
size="tiny"
variant="smooth"
color-scheme="alert"
icon="delete"
@click="emits('delete-folders')"
/>
</div>
<woot-button
v-else
v-tooltip.right="$t('FILTER.TOOLTIP_LABEL')"
variant="smooth"
color-scheme="secondary"
icon="filter"
size="tiny"
@click="emits('filters-modal')"
/>
<conversation-basic-filter
v-if="!hasAppliedFiltersOrActiveFolders"
@changeFilter="onBasicFilterChange"
/>
</div>
</div>
</template>

View File

@@ -73,25 +73,13 @@
</woot-dropdown-item> </woot-dropdown-item>
</woot-dropdown-menu> </woot-dropdown-menu>
</div> </div>
<woot-modal
:show.sync="showCustomSnoozeModal"
:on-close="hideCustomSnoozeModal"
>
<custom-snooze-modal
@close="hideCustomSnoozeModal"
@choose-time="chooseSnoozeTime"
/>
</woot-modal>
</div> </div>
</template> </template>
<script> <script>
import { getUnixTime } from 'date-fns';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin'; import alertMixin from 'shared/mixins/alertMixin';
import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal.vue';
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins'; import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers';
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue'; import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue'; import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
@@ -99,14 +87,12 @@ import wootConstants from 'dashboard/constants/globals';
import { import {
CMD_REOPEN_CONVERSATION, CMD_REOPEN_CONVERSATION,
CMD_RESOLVE_CONVERSATION, CMD_RESOLVE_CONVERSATION,
CMD_SNOOZE_CONVERSATION,
} from '../../routes/dashboard/commands/commandBarBusEvents'; } from '../../routes/dashboard/commands/commandBarBusEvents';
export default { export default {
components: { components: {
WootDropdownItem, WootDropdownItem,
WootDropdownMenu, WootDropdownMenu,
CustomSnoozeModal,
}, },
mixins: [alertMixin, keyboardEventListenerMixins], mixins: [alertMixin, keyboardEventListenerMixins],
props: { conversationId: { type: [String, Number], required: true } }, props: { conversationId: { type: [String, Number], required: true } },
@@ -115,7 +101,6 @@ export default {
isLoading: false, isLoading: false,
showActionsDropdown: false, showActionsDropdown: false,
STATUS_TYPE: wootConstants.STATUS_TYPE, STATUS_TYPE: wootConstants.STATUS_TYPE,
showCustomSnoozeModal: false,
}; };
}, },
computed: { computed: {
@@ -143,12 +128,10 @@ export default {
}, },
}, },
mounted() { mounted() {
bus.$on(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation);
bus.$on(CMD_REOPEN_CONVERSATION, this.onCmdOpenConversation); bus.$on(CMD_REOPEN_CONVERSATION, this.onCmdOpenConversation);
bus.$on(CMD_RESOLVE_CONVERSATION, this.onCmdResolveConversation); bus.$on(CMD_RESOLVE_CONVERSATION, this.onCmdResolveConversation);
}, },
destroyed() { destroyed() {
bus.$off(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation);
bus.$off(CMD_REOPEN_CONVERSATION, this.onCmdOpenConversation); bus.$off(CMD_REOPEN_CONVERSATION, this.onCmdOpenConversation);
bus.$off(CMD_RESOLVE_CONVERSATION, this.onCmdResolveConversation); bus.$off(CMD_RESOLVE_CONVERSATION, this.onCmdResolveConversation);
}, },
@@ -201,28 +184,6 @@ export default {
// error // error
} }
}, },
onCmdSnoozeConversation(snoozeType) {
if (snoozeType === wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME) {
this.showCustomSnoozeModal = true;
} else {
this.toggleStatus(
this.STATUS_TYPE.SNOOZED,
findSnoozeTime(snoozeType) || null
);
}
},
chooseSnoozeTime(customSnoozeTime) {
this.showCustomSnoozeModal = false;
if (customSnoozeTime) {
this.toggleStatus(
this.STATUS_TYPE.SNOOZED,
getUnixTime(customSnoozeTime)
);
}
},
hideCustomSnoozeModal() {
this.showCustomSnoozeModal = false;
},
onCmdOpenConversation() { onCmdOpenConversation() {
this.toggleStatus(this.STATUS_TYPE.OPEN); this.toggleStatus(this.STATUS_TYPE.OPEN);
}, },

View File

@@ -103,6 +103,7 @@
:status="chat.status" :status="chat.status"
:inbox-id="inbox.id" :inbox-id="inbox.id"
:priority="chat.priority" :priority="chat.priority"
:chat-id="chat.id"
:has-unread-messages="hasUnread" :has-unread-messages="hasUnread"
@update-conversation="onUpdateConversation" @update-conversation="onUpdateConversation"
@assign-agent="onAssignAgent" @assign-agent="onAssignAgent"

View File

@@ -16,7 +16,7 @@
/> />
</template> </template>
<menu-item <menu-item
v-if="show(snoozeOption.key)" v-if="showSnooze"
:option="snoozeOption" :option="snoozeOption"
variant="icon" variant="icon"
@click="snoozeConversation()" @click="snoozeConversation()"
@@ -86,6 +86,10 @@ export default {
}, },
mixins: [agentMixin], mixins: [agentMixin],
props: { props: {
chatId: {
type: Number,
default: null,
},
status: { status: {
type: String, type: String,
default: '', default: '',
@@ -205,6 +209,10 @@ export default {
...this.filteredAgentOnAvailability, ...this.filteredAgentOnAvailability,
]; ];
}, },
showSnooze() {
// Don't show snooze if the conversation is already snoozed/resolved/pending
return this.status === wootConstants.STATUS_TYPE.OPEN;
},
}, },
mounted() { mounted() {
this.$store.dispatch('inboxAssignableAgents/fetch', [this.inboxId]); this.$store.dispatch('inboxAssignableAgents/fetch', [this.inboxId]);
@@ -213,7 +221,8 @@ export default {
toggleStatus(status, snoozedUntil) { toggleStatus(status, snoozedUntil) {
this.$emit('update-conversation', status, snoozedUntil); this.$emit('update-conversation', status, snoozedUntil);
}, },
snoozeConversation() { async snoozeConversation() {
await this.$store.dispatch('setContextMenuChatId', this.chatId);
const ninja = document.querySelector('ninja-keys'); const ninja = document.querySelector('ninja-keys');
ninja.open({ parent: 'snooze_conversation' }); ninja.open({ parent: 'snooze_conversation' });
}, },

View File

@@ -52,8 +52,22 @@ export const validateLoggedInRoutes = (to, user, roleWiseRoutes) => {
return null; return null;
}; };
export const isAConversationRoute = routeName => export const isAConversationRoute = (
[ routeName,
includeBase = false,
includeExtended = true
) => {
const baseRoutes = [
'home',
'conversation_mentions',
'conversation_unattended',
'inbox_dashboard',
'label_conversations',
'team_conversations',
'folder_conversations',
'conversation_participating',
];
const extendedRoutes = [
'inbox_conversation', 'inbox_conversation',
'conversation_through_mentions', 'conversation_through_mentions',
'conversation_through_unattended', 'conversation_through_unattended',
@@ -62,7 +76,15 @@ export const isAConversationRoute = routeName =>
'conversations_through_team', 'conversations_through_team',
'conversations_through_folders', 'conversations_through_folders',
'conversation_through_participating', 'conversation_through_participating',
].includes(routeName); ];
const routes = [
...(includeBase ? baseRoutes : []),
...(includeExtended ? extendedRoutes : []),
];
return routes.includes(routeName);
};
export const getConversationDashboardRoute = routeName => { export const getConversationDashboardRoute = routeName => {
switch (routeName) { switch (routeName) {

View File

@@ -106,6 +106,51 @@ describe('isAConversationRoute', () => {
expect(isAConversationRoute('conversations_through_team')).toBe(true); expect(isAConversationRoute('conversations_through_team')).toBe(true);
expect(isAConversationRoute('dashboard')).toBe(false); expect(isAConversationRoute('dashboard')).toBe(false);
}); });
it('returns true if base conversation route name is provided and includeBase is true', () => {
expect(isAConversationRoute('home', true)).toBe(true);
expect(isAConversationRoute('conversation_mentions', true)).toBe(true);
expect(isAConversationRoute('conversation_unattended', true)).toBe(true);
expect(isAConversationRoute('inbox_dashboard', true)).toBe(true);
expect(isAConversationRoute('label_conversations', true)).toBe(true);
expect(isAConversationRoute('team_conversations', true)).toBe(true);
expect(isAConversationRoute('folder_conversations', true)).toBe(true);
expect(isAConversationRoute('conversation_participating', true)).toBe(true);
});
it('returns false if base conversation route name is provided and includeBase is false', () => {
expect(isAConversationRoute('home', false)).toBe(false);
expect(isAConversationRoute('conversation_mentions', false)).toBe(false);
expect(isAConversationRoute('conversation_unattended', false)).toBe(false);
expect(isAConversationRoute('inbox_dashboard', false)).toBe(false);
expect(isAConversationRoute('label_conversations', false)).toBe(false);
expect(isAConversationRoute('team_conversations', false)).toBe(false);
expect(isAConversationRoute('folder_conversations', false)).toBe(false);
expect(isAConversationRoute('conversation_participating', false)).toBe(
false
);
});
it('returns true if base conversation route name is provided and includeBase and includeExtended is true', () => {
expect(isAConversationRoute('home', true, true)).toBe(true);
expect(isAConversationRoute('conversation_mentions', true, true)).toBe(
true
);
expect(isAConversationRoute('conversation_unattended', true, true)).toBe(
true
);
expect(isAConversationRoute('inbox_dashboard', true, true)).toBe(true);
expect(isAConversationRoute('label_conversations', true, true)).toBe(true);
expect(isAConversationRoute('team_conversations', true, true)).toBe(true);
expect(isAConversationRoute('folder_conversations', true, true)).toBe(true);
expect(isAConversationRoute('conversation_participating', true, true)).toBe(
true
);
});
it('returns false if base conversation route name is not provided', () => {
expect(isAConversationRoute('')).toBe(false);
});
}); });
describe('getConversationDashboardRoute', () => { describe('getConversationDashboardRoute', () => {

View File

@@ -154,7 +154,7 @@
"UNTIL_TOMORROW": "Until tomorrow", "UNTIL_TOMORROW": "Until tomorrow",
"UNTIL_NEXT_MONTH": "Until next month", "UNTIL_NEXT_MONTH": "Until next month",
"AN_HOUR_FROM_NOW": "Until an hour from now", "AN_HOUR_FROM_NOW": "Until an hour from now",
"CUSTOM": "Custom...", "UNTIL_CUSTOM_TIME": "Custom...",
"CHANGE_APPEARANCE": "Change Appearance", "CHANGE_APPEARANCE": "Change Appearance",
"LIGHT_MODE": "Light", "LIGHT_MODE": "Light",
"DARK_MODE": "Dark", "DARK_MODE": "Dark",

View File

@@ -12,6 +12,8 @@ import {
ICON_RESOLVE_CONVERSATION, ICON_RESOLVE_CONVERSATION,
} from './CommandBarIcons'; } from './CommandBarIcons';
import { createSnoozeHandlers } from './commandBarActions';
const SNOOZE_OPTIONS = wootConstants.SNOOZE_OPTIONS; const SNOOZE_OPTIONS = wootConstants.SNOOZE_OPTIONS;
export const SNOOZE_CONVERSATION_BULK_ACTIONS = [ export const SNOOZE_CONVERSATION_BULK_ACTIONS = [
@@ -22,79 +24,11 @@ export const SNOOZE_CONVERSATION_BULK_ACTIONS = [
icon: ICON_SNOOZE_CONVERSATION, icon: ICON_SNOOZE_CONVERSATION,
children: Object.values(SNOOZE_OPTIONS), children: Object.values(SNOOZE_OPTIONS),
}, },
...createSnoozeHandlers(
{
id: SNOOZE_OPTIONS.UNTIL_NEXT_REPLY,
title: 'COMMAND_BAR.COMMANDS.UNTIL_NEXT_REPLY',
parent: 'bulk_action_snooze_conversation',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
icon: ICON_SNOOZE_CONVERSATION,
handler: () =>
bus.$emit(
CMD_BULK_ACTION_SNOOZE_CONVERSATION, CMD_BULK_ACTION_SNOOZE_CONVERSATION,
SNOOZE_OPTIONS.UNTIL_NEXT_REPLY 'bulk_action_snooze_conversation',
'COMMAND_BAR.SECTIONS.BULK_ACTIONS'
), ),
},
{
id: SNOOZE_OPTIONS.AN_HOUR_FROM_NOW,
title: 'COMMAND_BAR.COMMANDS.AN_HOUR_FROM_NOW',
parent: 'bulk_action_snooze_conversation',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
icon: ICON_SNOOZE_CONVERSATION,
handler: () =>
bus.$emit(
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
SNOOZE_OPTIONS.AN_HOUR_FROM_NOW
),
},
{
id: SNOOZE_OPTIONS.UNTIL_TOMORROW,
title: 'COMMAND_BAR.COMMANDS.UNTIL_TOMORROW',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
parent: 'bulk_action_snooze_conversation',
icon: ICON_SNOOZE_CONVERSATION,
handler: () =>
bus.$emit(
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
SNOOZE_OPTIONS.UNTIL_TOMORROW
),
},
{
id: SNOOZE_OPTIONS.UNTIL_NEXT_WEEK,
title: 'COMMAND_BAR.COMMANDS.UNTIL_NEXT_WEEK',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
parent: 'bulk_action_snooze_conversation',
icon: ICON_SNOOZE_CONVERSATION,
handler: () =>
bus.$emit(
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
SNOOZE_OPTIONS.UNTIL_NEXT_WEEK
),
},
{
id: SNOOZE_OPTIONS.UNTIL_NEXT_MONTH,
title: 'COMMAND_BAR.COMMANDS.UNTIL_NEXT_MONTH',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
parent: 'bulk_action_snooze_conversation',
icon: ICON_SNOOZE_CONVERSATION,
handler: () =>
bus.$emit(
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
SNOOZE_OPTIONS.UNTIL_NEXT_MONTH
),
},
{
id: SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME,
title: 'COMMAND_BAR.COMMANDS.CUSTOM',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
parent: 'bulk_action_snooze_conversation',
icon: ICON_SNOOZE_CONVERSATION,
handler: () =>
bus.$emit(
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME
),
},
]; ];
export const RESOLVED_CONVERSATION_BULK_ACTIONS = [ export const RESOLVED_CONVERSATION_BULK_ACTIONS = [

View File

@@ -30,6 +30,17 @@ export const OPEN_CONVERSATION_ACTIONS = [
}, },
]; ];
export const createSnoozeHandlers = (busEventName, parentId, section) => {
return Object.values(SNOOZE_OPTIONS).map(option => ({
id: option,
title: `COMMAND_BAR.COMMANDS.${option.toUpperCase()}`,
parent: parentId,
section: section,
icon: ICON_SNOOZE_CONVERSATION,
handler: () => bus.$emit(busEventName, option),
}));
};
export const SNOOZE_CONVERSATION_ACTIONS = [ export const SNOOZE_CONVERSATION_ACTIONS = [
{ {
id: 'snooze_conversation', id: 'snooze_conversation',
@@ -37,61 +48,11 @@ export const SNOOZE_CONVERSATION_ACTIONS = [
icon: ICON_SNOOZE_CONVERSATION, icon: ICON_SNOOZE_CONVERSATION,
children: Object.values(SNOOZE_OPTIONS), children: Object.values(SNOOZE_OPTIONS),
}, },
...createSnoozeHandlers(
{ CMD_SNOOZE_CONVERSATION,
id: SNOOZE_OPTIONS.UNTIL_NEXT_REPLY, 'snooze_conversation',
title: 'COMMAND_BAR.COMMANDS.UNTIL_NEXT_REPLY', 'COMMAND_BAR.SECTIONS.SNOOZE_CONVERSATION'
parent: 'snooze_conversation', ),
section: 'COMMAND_BAR.SECTIONS.SNOOZE_CONVERSATION',
icon: ICON_SNOOZE_CONVERSATION,
handler: () =>
bus.$emit(CMD_SNOOZE_CONVERSATION, SNOOZE_OPTIONS.UNTIL_NEXT_REPLY),
},
{
id: SNOOZE_OPTIONS.AN_HOUR_FROM_NOW,
title: 'COMMAND_BAR.COMMANDS.AN_HOUR_FROM_NOW',
parent: 'snooze_conversation',
section: 'COMMAND_BAR.SECTIONS.SNOOZE_CONVERSATION',
icon: ICON_SNOOZE_CONVERSATION,
handler: () =>
bus.$emit(CMD_SNOOZE_CONVERSATION, SNOOZE_OPTIONS.AN_HOUR_FROM_NOW),
},
{
id: SNOOZE_OPTIONS.UNTIL_TOMORROW,
title: 'COMMAND_BAR.COMMANDS.UNTIL_TOMORROW',
section: 'COMMAND_BAR.SECTIONS.SNOOZE_CONVERSATION',
parent: 'snooze_conversation',
icon: ICON_SNOOZE_CONVERSATION,
handler: () =>
bus.$emit(CMD_SNOOZE_CONVERSATION, SNOOZE_OPTIONS.UNTIL_TOMORROW),
},
{
id: SNOOZE_OPTIONS.UNTIL_NEXT_WEEK,
title: 'COMMAND_BAR.COMMANDS.UNTIL_NEXT_WEEK',
section: 'COMMAND_BAR.SECTIONS.SNOOZE_CONVERSATION',
parent: 'snooze_conversation',
icon: ICON_SNOOZE_CONVERSATION,
handler: () =>
bus.$emit(CMD_SNOOZE_CONVERSATION, SNOOZE_OPTIONS.UNTIL_NEXT_WEEK),
},
{
id: SNOOZE_OPTIONS.UNTIL_NEXT_MONTH,
title: 'COMMAND_BAR.COMMANDS.UNTIL_NEXT_MONTH',
section: 'COMMAND_BAR.SECTIONS.SNOOZE_CONVERSATION',
parent: 'snooze_conversation',
icon: ICON_SNOOZE_CONVERSATION,
handler: () =>
bus.$emit(CMD_SNOOZE_CONVERSATION, SNOOZE_OPTIONS.UNTIL_NEXT_MONTH),
},
{
id: SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME,
title: 'COMMAND_BAR.COMMANDS.CUSTOM',
section: 'COMMAND_BAR.SECTIONS.SNOOZE_CONVERSATION',
parent: 'snooze_conversation',
icon: ICON_SNOOZE_CONVERSATION,
handler: () =>
bus.$emit(CMD_SNOOZE_CONVERSATION, SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME),
},
]; ];
export const RESOLVED_CONVERSATION_ACTIONS = [ export const RESOLVED_CONVERSATION_ACTIONS = [

View File

@@ -6,11 +6,13 @@
hideBreadcrumbs hideBreadcrumbs
:placeholder="placeholder" :placeholder="placeholder"
@selected="onSelected" @selected="onSelected"
@closed="onClosed"
/> />
</template> </template>
<script> <script>
import 'ninja-keys'; import '@chatwoot/ninja-keys';
import wootConstants from 'dashboard/constants/globals';
import conversationHotKeysMixin from './conversationHotKeys'; import conversationHotKeysMixin from './conversationHotKeys';
import bulkActionsHotKeysMixin from './bulkActionsHotKeys'; import bulkActionsHotKeysMixin from './bulkActionsHotKeys';
import inboxHotKeysMixin from './inboxHotKeys'; import inboxHotKeysMixin from './inboxHotKeys';
@@ -34,6 +36,14 @@ export default {
appearanceHotKeys, appearanceHotKeys,
goToCommandHotKeys, goToCommandHotKeys,
], ],
data() {
return {
// Added selectedSnoozeType to track the selected snooze type
// So if the selected snooze type is "custom snooze" then we set selectedSnoozeType with the CMD action id
// So that we can track the selected snooze type and when we close the command bar
selectedSnoozeType: null,
};
},
computed: { computed: {
placeholder() { placeholder() {
return this.$t('COMMAND_BAR.SEARCH_PLACEHOLDER'); return this.$t('COMMAND_BAR.SEARCH_PLACEHOLDER');
@@ -67,14 +77,35 @@ export default {
this.$refs.ninjakeys.data = this.hotKeys; this.$refs.ninjakeys.data = this.hotKeys;
}, },
onSelected(item) { onSelected(item) {
const { detail: { action: { title = null, section = null } = {} } = {} } = const {
item; detail: {
action: { title = null, section = null, id = null } = {},
} = {},
} = item;
// Added this condition to prevent setting the selectedSnoozeType to null
// When we select the "custom snooze" (CMD bar will close and the custom snooze modal will open)
if (id === wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME) {
this.selectedSnoozeType =
wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME;
} else {
this.selectedSnoozeType = null;
}
this.$track(GENERAL_EVENTS.COMMAND_BAR, { this.$track(GENERAL_EVENTS.COMMAND_BAR, {
section, section,
action: title, action: title,
}); });
this.setCommandbarData(); this.setCommandbarData();
}, },
onClosed() {
// If the selectedSnoozeType is not "SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME (custom snooze)" then we set the context menu chat id to null
// Else we do nothing and its handled in the ChatList.vue hideCustomSnoozeModal() method
if (
this.selectedSnoozeType !==
wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME
) {
this.$store.dispatch('setContextMenuChatId', null);
}
},
}, },
}; };
</script> </script>

View File

@@ -55,11 +55,15 @@ export default {
replyMode() { replyMode() {
this.setCommandbarData(); this.setCommandbarData();
}, },
contextMenuChatId() {
this.setCommandbarData();
},
}, },
computed: { computed: {
...mapGetters({ ...mapGetters({
currentChat: 'getSelectedChat', currentChat: 'getSelectedChat',
replyMode: 'draftMessages/getReplyEditorMode', replyMode: 'draftMessages/getReplyEditorMode',
contextMenuChatId: 'getContextMenuChatId',
}), }),
draftMessage() { draftMessage() {
return this.$store.getters['draftMessages/get'](this.draftKey); return this.$store.getters['draftMessages/get'](this.draftKey);
@@ -93,6 +97,7 @@ export default {
} }
return this.prepareActions(actions); return this.prepareActions(actions);
}, },
priorityOptions() { priorityOptions() {
return [ return [
{ {
@@ -327,11 +332,21 @@ export default {
]; ];
}, },
conversationHotKeys() { isConversationOrInboxRoute() {
if ( return (
isAConversationRoute(this.$route.name) || isAConversationRoute(this.$route.name) ||
isAInboxViewRoute(this.$route.name) isAInboxViewRoute(this.$route.name)
) { );
},
shouldShowSnoozeOption() {
return (
isAConversationRoute(this.$route.name, true, false) &&
this.contextMenuChatId
);
},
getDefaultConversationHotKeys() {
const defaultConversationHotKeys = [ const defaultConversationHotKeys = [
...this.statusActions, ...this.statusActions,
...this.conversationAdditionalActions, ...this.conversationAdditionalActions,
@@ -344,8 +359,15 @@ export default {
return [...defaultConversationHotKeys, ...this.AIAssistActions]; return [...defaultConversationHotKeys, ...this.AIAssistActions];
} }
return defaultConversationHotKeys; return defaultConversationHotKeys;
} },
conversationHotKeys() {
if (this.shouldShowSnoozeOption) {
return this.prepareActions(SNOOZE_CONVERSATION_ACTIONS);
}
if (this.isConversationOrInboxRoute) {
return this.getDefaultConversationHotKeys;
}
return []; return [];
}, },
}, },

View File

@@ -466,6 +466,10 @@ const actions = {
commit(types.ASSIGN_PRIORITY, { priority, conversationId }); commit(types.ASSIGN_PRIORITY, { priority, conversationId });
}, },
setContextMenuChatId({ commit }, chatId) {
commit(types.SET_CONTEXT_MENU_CHAT_ID, chatId);
},
...messageReadActions, ...messageReadActions,
...messageTranslateActions, ...messageTranslateActions,
}; };

View File

@@ -100,6 +100,10 @@ const getters = {
getConversationLastSeen: _state => { getConversationLastSeen: _state => {
return _state.conversationLastSeen; return _state.conversationLastSeen;
}, },
getContextMenuChatId: _state => {
return _state.contextMenuChatId;
},
}; };
export default getters; export default getters;

View File

@@ -15,6 +15,7 @@ const state = {
currentInbox: null, currentInbox: null,
selectedChatId: null, selectedChatId: null,
appliedFilters: [], appliedFilters: [],
contextMenuChatId: null,
conversationParticipants: [], conversationParticipants: [],
conversationLastSeen: null, conversationLastSeen: null,
syncConversationsMessages: {}, syncConversationsMessages: {},
@@ -281,6 +282,10 @@ export const mutations = {
) { ) {
_state.syncConversationsMessages[conversationId] = messageId; _state.syncConversationsMessages[conversationId] = messageId;
}, },
[types.SET_CONTEXT_MENU_CHAT_ID](_state, chatId) {
_state.contextMenuChatId = chatId;
},
}; };
export default { export default {

View File

@@ -652,4 +652,11 @@ describe('#addMentions', () => {
]); ]);
}); });
}); });
describe('#setContextMenuChatId', () => {
it('sets the context menu chat id', () => {
actions.setContextMenuChatId({ commit }, 1);
expect(commit.mock.calls).toEqual([[types.SET_CONTEXT_MENU_CHAT_ID, 1]]);
});
});
}); });

View File

@@ -272,4 +272,11 @@ describe('#getters', () => {
]); ]);
}); });
}); });
describe('#getContextMenuChatId', () => {
it('returns the context menu chat id', () => {
const state = { contextMenuChatId: 1 };
expect(getters.getContextMenuChatId(state)).toEqual(1);
});
});
}); });

View File

@@ -403,4 +403,12 @@ describe('#mutations', () => {
expect(state.allConversations[0].attachments).toHaveLength(1); expect(state.allConversations[0].attachments).toHaveLength(1);
}); });
}); });
describe('#SET_CONTEXT_MENU_CHAT_ID', () => {
it('sets the context menu chat id', () => {
const state = { contextMenuChatId: 1 };
mutations[types.SET_CONTEXT_MENU_CHAT_ID](state, 2);
expect(state.contextMenuChatId).toEqual(2);
});
});
}); });

View File

@@ -58,6 +58,8 @@ export default {
SET_CONVERSATION_CAN_REPLY: 'SET_CONVERSATION_CAN_REPLY', SET_CONVERSATION_CAN_REPLY: 'SET_CONVERSATION_CAN_REPLY',
SET_CONTEXT_MENU_CHAT_ID: 'SET_CONTEXT_MENU_CHAT_ID',
// Inboxes // Inboxes
SET_INBOXES_UI_FLAG: 'SET_INBOXES_UI_FLAG', SET_INBOXES_UI_FLAG: 'SET_INBOXES_UI_FLAG',
SET_INBOXES: 'SET_INBOXES', SET_INBOXES: 'SET_INBOXES',

View File

@@ -67,7 +67,7 @@
"markdown-it": "^13.0.2", "markdown-it": "^13.0.2",
"markdown-it-link-attributes": "^4.0.1", "markdown-it-link-attributes": "^4.0.1",
"md5": "^2.3.0", "md5": "^2.3.0",
"ninja-keys": "^1.2.2", "@chatwoot/ninja-keys": "1.2.3",
"opus-recorder": "^8.0.5", "opus-recorder": "^8.0.5",
"postcss": "^8.4.31", "postcss": "^8.4.31",
"postcss-loader": "^4.2.0", "postcss-loader": "^4.2.0",

View File

@@ -3156,6 +3156,15 @@
"@braid/vue-formulate-i18n" "^1.16.0" "@braid/vue-formulate-i18n" "^1.16.0"
is-plain-object "^3.0.1" is-plain-object "^3.0.1"
"@chatwoot/ninja-keys@1.2.3":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@chatwoot/ninja-keys/-/ninja-keys-1.2.3.tgz#3c3f2b505f091ef4707fd1da39bb2ec6d12e7824"
integrity sha512-xM8d9P5ikDMZm2WbaCTk/TW5HFauylrU3cJ75fq5je6ixKwyhl/0kZbVN/vbbZN4+AUX/OaSIn6IJbtCgIF67g==
dependencies:
"@material/mwc-icon" "0.25.3"
hotkeys-js "3.8.7"
lit "2.2.6"
"@chatwoot/prosemirror-schema@1.0.5": "@chatwoot/prosemirror-schema@1.0.5":
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/@chatwoot/prosemirror-schema/-/prosemirror-schema-1.0.5.tgz#d6053692beae59d466ac0b04128fa157f59eb176" resolved "https://registry.yarnpkg.com/@chatwoot/prosemirror-schema/-/prosemirror-schema-1.0.5.tgz#d6053692beae59d466ac0b04128fa157f59eb176"
@@ -15083,15 +15092,6 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
ninja-keys@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/ninja-keys/-/ninja-keys-1.2.2.tgz#c1e1ec1a98aee3a977ee77157ac4aa865348be88"
integrity sha512-ylo8jzKowi3XBHkgHRjBJaKQkl32WRLr7kRiA0ajiku11vHRDJ2xANtTScR5C7XlDwKEOYvUPesCKacUeeLAYw==
dependencies:
"@material/mwc-icon" "0.25.3"
hotkeys-js "3.8.7"
lit "2.2.6"
no-case@^3.0.4: no-case@^3.0.4:
version "3.0.4" version "3.0.4"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"