mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-31 19:17:48 +00:00
fix: Right click Snooze is not working (#9498)
This commit is contained in:
@@ -7,79 +7,17 @@
|
||||
]"
|
||||
>
|
||||
<slot />
|
||||
<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="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>
|
||||
<chat-list-header
|
||||
:page-title="pageTitle"
|
||||
:has-applied-filters="hasAppliedFilters"
|
||||
:has-active-folders="hasActiveFolders"
|
||||
:active-status="activeStatus"
|
||||
@add-folders="onClickOpenAddFoldersModal"
|
||||
@delete-folders="onClickOpenDeleteFoldersModal"
|
||||
@filters-modal="onToggleAdvanceFiltersModal"
|
||||
@reset-filters="resetAndFetchData"
|
||||
@basic-filter-change="onBasicFilterChange"
|
||||
/>
|
||||
|
||||
<add-custom-views
|
||||
v-if="showAddFoldersModal"
|
||||
@@ -173,6 +111,15 @@
|
||||
@updateFolder="onUpdateSavedFilter"
|
||||
/>
|
||||
</woot-modal>
|
||||
<woot-modal
|
||||
:show.sync="showCustomSnoozeModal"
|
||||
:on-close="hideCustomSnoozeModal"
|
||||
>
|
||||
<custom-snooze-modal
|
||||
@close="hideCustomSnoozeModal"
|
||||
@choose-time="chooseSnoozeTime"
|
||||
/>
|
||||
</woot-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -180,8 +127,8 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
import VirtualList from 'vue-virtual-scroll-list';
|
||||
|
||||
import ChatListHeader from './ChatListHeader.vue';
|
||||
import ConversationAdvancedFilter from './widgets/conversation/ConversationAdvancedFilter.vue';
|
||||
import ConversationBasicFilter from './widgets/conversation/ConversationBasicFilter.vue';
|
||||
import ChatTypeTabs from './widgets/ChatTypeTabs.vue';
|
||||
import ConversationItem from './ConversationItem.vue';
|
||||
import timeMixin from '../mixins/time';
|
||||
@@ -205,10 +152,15 @@ import {
|
||||
isOnUnattendedView,
|
||||
} from '../store/modules/conversations/helpers/actionHelpers';
|
||||
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';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ChatListHeader,
|
||||
AddCustomViews,
|
||||
ChatTypeTabs,
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
@@ -216,9 +168,9 @@ export default {
|
||||
ConversationAdvancedFilter,
|
||||
DeleteCustomViews,
|
||||
ConversationBulkActions,
|
||||
ConversationBasicFilter,
|
||||
IntersectionObserver,
|
||||
VirtualList,
|
||||
CustomSnoozeModal,
|
||||
},
|
||||
mixins: [
|
||||
timeMixin,
|
||||
@@ -295,6 +247,7 @@ export default {
|
||||
root: this.$refs.conversationList,
|
||||
rootMargin: '100px 0px 100px 0px',
|
||||
},
|
||||
showCustomSnoozeModal: false,
|
||||
|
||||
itemComponent: ConversationItem,
|
||||
// virtualListExtraProps is to pass the props to the conversationItem component.
|
||||
@@ -329,12 +282,13 @@ export default {
|
||||
campaigns: 'campaigns/getAllCampaigns',
|
||||
labels: 'labels/getLabels',
|
||||
selectedConversations: 'bulkActions/getSelectedConversationIds',
|
||||
contextMenuChatId: 'getContextMenuChatId',
|
||||
}),
|
||||
hasAppliedFilters() {
|
||||
return this.appliedFilters.length !== 0;
|
||||
},
|
||||
hasActiveFolders() {
|
||||
return this.activeFolder && this.foldersId !== 0;
|
||||
return Boolean(this.activeFolder && this.foldersId !== 0);
|
||||
},
|
||||
hasAppliedFiltersOrActiveFolders() {
|
||||
return this.hasAppliedFilters || this.hasActiveFolders;
|
||||
@@ -558,6 +512,11 @@ export default {
|
||||
bus.$on('fetch_conversation_stats', () => {
|
||||
this.$store.dispatch('conversationStats/get', this.conversationFilters);
|
||||
});
|
||||
|
||||
bus.$on(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation);
|
||||
},
|
||||
beforeDestroy() {
|
||||
bus.$off(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation);
|
||||
},
|
||||
methods: {
|
||||
updateVirtualListProps(key, value) {
|
||||
@@ -1034,6 +993,43 @@ export default {
|
||||
onContextMenuToggle(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>
|
||||
|
||||
115
app/javascript/dashboard/components/ChatListHeader.vue
Normal file
115
app/javascript/dashboard/components/ChatListHeader.vue
Normal 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>
|
||||
@@ -73,25 +73,13 @@
|
||||
</woot-dropdown-item>
|
||||
</woot-dropdown-menu>
|
||||
</div>
|
||||
<woot-modal
|
||||
:show.sync="showCustomSnoozeModal"
|
||||
:on-close="hideCustomSnoozeModal"
|
||||
>
|
||||
<custom-snooze-modal
|
||||
@close="hideCustomSnoozeModal"
|
||||
@choose-time="chooseSnoozeTime"
|
||||
/>
|
||||
</woot-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getUnixTime } from 'date-fns';
|
||||
import { mapGetters } from 'vuex';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal.vue';
|
||||
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';
|
||||
|
||||
@@ -99,14 +87,12 @@ import wootConstants from 'dashboard/constants/globals';
|
||||
import {
|
||||
CMD_REOPEN_CONVERSATION,
|
||||
CMD_RESOLVE_CONVERSATION,
|
||||
CMD_SNOOZE_CONVERSATION,
|
||||
} from '../../routes/dashboard/commands/commandBarBusEvents';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
WootDropdownItem,
|
||||
WootDropdownMenu,
|
||||
CustomSnoozeModal,
|
||||
},
|
||||
mixins: [alertMixin, keyboardEventListenerMixins],
|
||||
props: { conversationId: { type: [String, Number], required: true } },
|
||||
@@ -115,7 +101,6 @@ export default {
|
||||
isLoading: false,
|
||||
showActionsDropdown: false,
|
||||
STATUS_TYPE: wootConstants.STATUS_TYPE,
|
||||
showCustomSnoozeModal: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -143,12 +128,10 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
bus.$on(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation);
|
||||
bus.$on(CMD_REOPEN_CONVERSATION, this.onCmdOpenConversation);
|
||||
bus.$on(CMD_RESOLVE_CONVERSATION, this.onCmdResolveConversation);
|
||||
},
|
||||
destroyed() {
|
||||
bus.$off(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation);
|
||||
bus.$off(CMD_REOPEN_CONVERSATION, this.onCmdOpenConversation);
|
||||
bus.$off(CMD_RESOLVE_CONVERSATION, this.onCmdResolveConversation);
|
||||
},
|
||||
@@ -201,28 +184,6 @@ export default {
|
||||
// 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() {
|
||||
this.toggleStatus(this.STATUS_TYPE.OPEN);
|
||||
},
|
||||
|
||||
@@ -103,6 +103,7 @@
|
||||
:status="chat.status"
|
||||
:inbox-id="inbox.id"
|
||||
:priority="chat.priority"
|
||||
:chat-id="chat.id"
|
||||
:has-unread-messages="hasUnread"
|
||||
@update-conversation="onUpdateConversation"
|
||||
@assign-agent="onAssignAgent"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
/>
|
||||
</template>
|
||||
<menu-item
|
||||
v-if="show(snoozeOption.key)"
|
||||
v-if="showSnooze"
|
||||
:option="snoozeOption"
|
||||
variant="icon"
|
||||
@click="snoozeConversation()"
|
||||
@@ -86,6 +86,10 @@ export default {
|
||||
},
|
||||
mixins: [agentMixin],
|
||||
props: {
|
||||
chatId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
default: '',
|
||||
@@ -205,6 +209,10 @@ export default {
|
||||
...this.filteredAgentOnAvailability,
|
||||
];
|
||||
},
|
||||
showSnooze() {
|
||||
// Don't show snooze if the conversation is already snoozed/resolved/pending
|
||||
return this.status === wootConstants.STATUS_TYPE.OPEN;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('inboxAssignableAgents/fetch', [this.inboxId]);
|
||||
@@ -213,7 +221,8 @@ export default {
|
||||
toggleStatus(status, snoozedUntil) {
|
||||
this.$emit('update-conversation', status, snoozedUntil);
|
||||
},
|
||||
snoozeConversation() {
|
||||
async snoozeConversation() {
|
||||
await this.$store.dispatch('setContextMenuChatId', this.chatId);
|
||||
const ninja = document.querySelector('ninja-keys');
|
||||
ninja.open({ parent: 'snooze_conversation' });
|
||||
},
|
||||
|
||||
@@ -52,8 +52,22 @@ export const validateLoggedInRoutes = (to, user, roleWiseRoutes) => {
|
||||
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',
|
||||
'conversation_through_mentions',
|
||||
'conversation_through_unattended',
|
||||
@@ -62,7 +76,15 @@ export const isAConversationRoute = routeName =>
|
||||
'conversations_through_team',
|
||||
'conversations_through_folders',
|
||||
'conversation_through_participating',
|
||||
].includes(routeName);
|
||||
];
|
||||
|
||||
const routes = [
|
||||
...(includeBase ? baseRoutes : []),
|
||||
...(includeExtended ? extendedRoutes : []),
|
||||
];
|
||||
|
||||
return routes.includes(routeName);
|
||||
};
|
||||
|
||||
export const getConversationDashboardRoute = routeName => {
|
||||
switch (routeName) {
|
||||
|
||||
@@ -106,6 +106,51 @@ describe('isAConversationRoute', () => {
|
||||
expect(isAConversationRoute('conversations_through_team')).toBe(true);
|
||||
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', () => {
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
"UNTIL_TOMORROW": "Until tomorrow",
|
||||
"UNTIL_NEXT_MONTH": "Until next month",
|
||||
"AN_HOUR_FROM_NOW": "Until an hour from now",
|
||||
"CUSTOM": "Custom...",
|
||||
"UNTIL_CUSTOM_TIME": "Custom...",
|
||||
"CHANGE_APPEARANCE": "Change Appearance",
|
||||
"LIGHT_MODE": "Light",
|
||||
"DARK_MODE": "Dark",
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
ICON_RESOLVE_CONVERSATION,
|
||||
} from './CommandBarIcons';
|
||||
|
||||
import { createSnoozeHandlers } from './commandBarActions';
|
||||
|
||||
const SNOOZE_OPTIONS = wootConstants.SNOOZE_OPTIONS;
|
||||
|
||||
export const SNOOZE_CONVERSATION_BULK_ACTIONS = [
|
||||
@@ -22,79 +24,11 @@ export const SNOOZE_CONVERSATION_BULK_ACTIONS = [
|
||||
icon: ICON_SNOOZE_CONVERSATION,
|
||||
children: Object.values(SNOOZE_OPTIONS),
|
||||
},
|
||||
|
||||
{
|
||||
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,
|
||||
SNOOZE_OPTIONS.UNTIL_NEXT_REPLY
|
||||
),
|
||||
},
|
||||
{
|
||||
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
|
||||
),
|
||||
},
|
||||
...createSnoozeHandlers(
|
||||
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
|
||||
'bulk_action_snooze_conversation',
|
||||
'COMMAND_BAR.SECTIONS.BULK_ACTIONS'
|
||||
),
|
||||
];
|
||||
|
||||
export const RESOLVED_CONVERSATION_BULK_ACTIONS = [
|
||||
|
||||
@@ -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 = [
|
||||
{
|
||||
id: 'snooze_conversation',
|
||||
@@ -37,61 +48,11 @@ export const SNOOZE_CONVERSATION_ACTIONS = [
|
||||
icon: ICON_SNOOZE_CONVERSATION,
|
||||
children: Object.values(SNOOZE_OPTIONS),
|
||||
},
|
||||
|
||||
{
|
||||
id: SNOOZE_OPTIONS.UNTIL_NEXT_REPLY,
|
||||
title: 'COMMAND_BAR.COMMANDS.UNTIL_NEXT_REPLY',
|
||||
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),
|
||||
},
|
||||
...createSnoozeHandlers(
|
||||
CMD_SNOOZE_CONVERSATION,
|
||||
'snooze_conversation',
|
||||
'COMMAND_BAR.SECTIONS.SNOOZE_CONVERSATION'
|
||||
),
|
||||
];
|
||||
|
||||
export const RESOLVED_CONVERSATION_ACTIONS = [
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
hideBreadcrumbs
|
||||
:placeholder="placeholder"
|
||||
@selected="onSelected"
|
||||
@closed="onClosed"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import 'ninja-keys';
|
||||
import '@chatwoot/ninja-keys';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import conversationHotKeysMixin from './conversationHotKeys';
|
||||
import bulkActionsHotKeysMixin from './bulkActionsHotKeys';
|
||||
import inboxHotKeysMixin from './inboxHotKeys';
|
||||
@@ -34,6 +36,14 @@ export default {
|
||||
appearanceHotKeys,
|
||||
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: {
|
||||
placeholder() {
|
||||
return this.$t('COMMAND_BAR.SEARCH_PLACEHOLDER');
|
||||
@@ -67,14 +77,35 @@ export default {
|
||||
this.$refs.ninjakeys.data = this.hotKeys;
|
||||
},
|
||||
onSelected(item) {
|
||||
const { detail: { action: { title = null, section = null } = {} } = {} } =
|
||||
item;
|
||||
const {
|
||||
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, {
|
||||
section,
|
||||
action: title,
|
||||
});
|
||||
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>
|
||||
|
||||
@@ -55,11 +55,15 @@ export default {
|
||||
replyMode() {
|
||||
this.setCommandbarData();
|
||||
},
|
||||
contextMenuChatId() {
|
||||
this.setCommandbarData();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentChat: 'getSelectedChat',
|
||||
replyMode: 'draftMessages/getReplyEditorMode',
|
||||
contextMenuChatId: 'getContextMenuChatId',
|
||||
}),
|
||||
draftMessage() {
|
||||
return this.$store.getters['draftMessages/get'](this.draftKey);
|
||||
@@ -93,6 +97,7 @@ export default {
|
||||
}
|
||||
return this.prepareActions(actions);
|
||||
},
|
||||
|
||||
priorityOptions() {
|
||||
return [
|
||||
{
|
||||
@@ -327,25 +332,42 @@ export default {
|
||||
];
|
||||
},
|
||||
|
||||
conversationHotKeys() {
|
||||
if (
|
||||
isConversationOrInboxRoute() {
|
||||
return (
|
||||
isAConversationRoute(this.$route.name) ||
|
||||
isAInboxViewRoute(this.$route.name)
|
||||
) {
|
||||
const defaultConversationHotKeys = [
|
||||
...this.statusActions,
|
||||
...this.conversationAdditionalActions,
|
||||
...this.assignAgentActions,
|
||||
...this.assignTeamActions,
|
||||
...this.labelActions,
|
||||
...this.assignPriorityActions,
|
||||
];
|
||||
if (this.isAIIntegrationEnabled) {
|
||||
return [...defaultConversationHotKeys, ...this.AIAssistActions];
|
||||
}
|
||||
return defaultConversationHotKeys;
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
shouldShowSnoozeOption() {
|
||||
return (
|
||||
isAConversationRoute(this.$route.name, true, false) &&
|
||||
this.contextMenuChatId
|
||||
);
|
||||
},
|
||||
|
||||
getDefaultConversationHotKeys() {
|
||||
const defaultConversationHotKeys = [
|
||||
...this.statusActions,
|
||||
...this.conversationAdditionalActions,
|
||||
...this.assignAgentActions,
|
||||
...this.assignTeamActions,
|
||||
...this.labelActions,
|
||||
...this.assignPriorityActions,
|
||||
];
|
||||
if (this.isAIIntegrationEnabled) {
|
||||
return [...defaultConversationHotKeys, ...this.AIAssistActions];
|
||||
}
|
||||
return defaultConversationHotKeys;
|
||||
},
|
||||
|
||||
conversationHotKeys() {
|
||||
if (this.shouldShowSnoozeOption) {
|
||||
return this.prepareActions(SNOOZE_CONVERSATION_ACTIONS);
|
||||
}
|
||||
if (this.isConversationOrInboxRoute) {
|
||||
return this.getDefaultConversationHotKeys;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
},
|
||||
|
||||
@@ -466,6 +466,10 @@ const actions = {
|
||||
commit(types.ASSIGN_PRIORITY, { priority, conversationId });
|
||||
},
|
||||
|
||||
setContextMenuChatId({ commit }, chatId) {
|
||||
commit(types.SET_CONTEXT_MENU_CHAT_ID, chatId);
|
||||
},
|
||||
|
||||
...messageReadActions,
|
||||
...messageTranslateActions,
|
||||
};
|
||||
|
||||
@@ -100,6 +100,10 @@ const getters = {
|
||||
getConversationLastSeen: _state => {
|
||||
return _state.conversationLastSeen;
|
||||
},
|
||||
|
||||
getContextMenuChatId: _state => {
|
||||
return _state.contextMenuChatId;
|
||||
},
|
||||
};
|
||||
|
||||
export default getters;
|
||||
|
||||
@@ -15,6 +15,7 @@ const state = {
|
||||
currentInbox: null,
|
||||
selectedChatId: null,
|
||||
appliedFilters: [],
|
||||
contextMenuChatId: null,
|
||||
conversationParticipants: [],
|
||||
conversationLastSeen: null,
|
||||
syncConversationsMessages: {},
|
||||
@@ -281,6 +282,10 @@ export const mutations = {
|
||||
) {
|
||||
_state.syncConversationsMessages[conversationId] = messageId;
|
||||
},
|
||||
|
||||
[types.SET_CONTEXT_MENU_CHAT_ID](_state, chatId) {
|
||||
_state.contextMenuChatId = chatId;
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
|
||||
@@ -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]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -403,4 +403,12 @@ describe('#mutations', () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,6 +58,8 @@ export default {
|
||||
|
||||
SET_CONVERSATION_CAN_REPLY: 'SET_CONVERSATION_CAN_REPLY',
|
||||
|
||||
SET_CONTEXT_MENU_CHAT_ID: 'SET_CONTEXT_MENU_CHAT_ID',
|
||||
|
||||
// Inboxes
|
||||
SET_INBOXES_UI_FLAG: 'SET_INBOXES_UI_FLAG',
|
||||
SET_INBOXES: 'SET_INBOXES',
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
"markdown-it": "^13.0.2",
|
||||
"markdown-it-link-attributes": "^4.0.1",
|
||||
"md5": "^2.3.0",
|
||||
"ninja-keys": "^1.2.2",
|
||||
"@chatwoot/ninja-keys": "1.2.3",
|
||||
"opus-recorder": "^8.0.5",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss-loader": "^4.2.0",
|
||||
|
||||
18
yarn.lock
18
yarn.lock
@@ -3156,6 +3156,15 @@
|
||||
"@braid/vue-formulate-i18n" "^1.16.0"
|
||||
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":
|
||||
version "1.0.5"
|
||||
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"
|
||||
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:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
|
||||
|
||||
Reference in New Issue
Block a user