Compare commits

...
@@ -1,55 +1,72 @@
<script> <script setup>
import { ref, provide } from 'vue'; import {
// composable ref,
computed,
watch,
onMounted,
onUnmounted,
nextTick,
provide,
getCurrentInstance,
} from 'vue';
import { useConfig } from 'dashboard/composables/useConfig'; import { useConfig } from 'dashboard/composables/useConfig';
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents'; import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
import { useAI } from 'dashboard/composables/useAI'; import { useAI } from 'dashboard/composables/useAI';
import { useSnakeCase } from 'dashboard/composables/useTransformKeys'; import { useSnakeCase } from 'dashboard/composables/useTransformKeys';
import { useStore, useMapGetter } from 'dashboard/composables/store';
import {
useInbox,
INBOX_FEATURES,
INBOX_FEATURE_MAP,
} from 'dashboard/composables/useInbox';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
// components
import ReplyBox from './ReplyBox.vue'; import ReplyBox from './ReplyBox.vue';
import MessageList from 'next/message/MessageList.vue'; import MessageList from 'next/message/MessageList.vue';
import ConversationLabelSuggestion from './conversation/LabelSuggestion.vue'; import ConversationLabelSuggestion from './conversation/LabelSuggestion.vue';
import Banner from 'dashboard/components/ui/Banner.vue'; import Banner from 'dashboard/components/ui/Banner.vue';
import Spinner from 'dashboard/components-next/spinner/Spinner.vue'; import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
// stores and apis
import { mapGetters } from 'vuex';
// mixins
import inboxMixin, { INBOX_FEATURES } from 'shared/mixins/inboxMixin';
// utils
import { emitter } from 'shared/helpers/mitt'; import { emitter } from 'shared/helpers/mitt';
import { getTypingUsersText } from '../../../helper/commons'; import { getTypingUsersText } from '../../../helper/commons';
import { calculateScrollTop } from './helpers/scrollTopCalculationHelper'; import { calculateScrollTop } from './helpers/scrollTopCalculationHelper';
import { LocalStorage } from 'shared/helpers/localStorage'; import { LocalStorage } from 'shared/helpers/localStorage';
import { import {
filterDuplicateSourceMessages, filterDuplicateSourceMessages,
getReadMessages,
getUnreadMessages, getUnreadMessages,
} from 'dashboard/helper/conversationHelper'; } from 'dashboard/helper/conversationHelper';
// constants
import { BUS_EVENTS } from 'shared/constants/busEvents'; import { BUS_EVENTS } from 'shared/constants/busEvents';
import { REPLY_POLICY } from 'shared/constants/links'; import { REPLY_POLICY } from 'shared/constants/links';
import wootConstants from 'dashboard/constants/globals'; import wootConstants from 'dashboard/constants/globals';
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage'; import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
import { INBOX_TYPES } from 'dashboard/helper/inbox'; import { INBOX_TYPES } from 'dashboard/helper/inbox';
export default { const store = useStore();
components: { const route = useRoute();
MessageList, const { t } = useI18n();
ReplyBox, const instance = getCurrentInstance();
Banner, const { isEnterprise } = useConfig();
ConversationLabelSuggestion,
Spinner, const {
}, isAIIntegrationEnabled,
mixins: [inboxMixin], isLabelSuggestionFeatureEnabled,
setup() { fetchIntegrationsIfRequired,
fetchLabelSuggestions,
} = useAI();
const isPopOutReplyBox = ref(false); const isPopOutReplyBox = ref(false);
const conversationPanelRef = ref(null); const conversationPanelRef = ref(null);
const { isEnterprise } = useConfig(); const isLoadingPrevious = ref(true);
const heightBeforeLoad = ref(null);
const scrollTopBeforeLoad = ref(null);
const conversationPanel = ref(null);
const hasUserScrolled = ref(false);
const isProgrammaticScroll = ref(false);
const messageSentSinceOpened = ref(false);
const labelSuggestions = ref([]);
const keyboardEvents = { const keyboardEvents = {
Escape: { Escape: {
@@ -61,127 +78,117 @@ export default {
useKeyboardEvents(keyboardEvents); useKeyboardEvents(keyboardEvents);
const {
isAIIntegrationEnabled,
isLabelSuggestionFeatureEnabled,
fetchIntegrationsIfRequired,
fetchLabelSuggestions,
} = useAI();
provide('contextMenuElementTarget', conversationPanelRef); provide('contextMenuElementTarget', conversationPanelRef);
return { const currentChat = useMapGetter('getSelectedChat');
isEnterprise, const currentUserId = useMapGetter('getCurrentUserID');
isPopOutReplyBox, const listLoadingStatus = useMapGetter('getAllMessagesLoaded');
isAIIntegrationEnabled, const currentAccountId = useMapGetter('getCurrentAccountId');
isLabelSuggestionFeatureEnabled, const instagramInboxByIdGetter = useMapGetter(
fetchIntegrationsIfRequired, 'inboxes/getInstagramInboxByInstagramId'
fetchLabelSuggestions,
conversationPanelRef,
};
},
data() {
return {
isLoadingPrevious: true,
heightBeforeLoad: null,
conversationPanel: null,
hasUserScrolled: false,
isProgrammaticScroll: false,
messageSentSinceOpened: false,
labelSuggestions: [],
};
},
computed: {
...mapGetters({
currentChat: 'getSelectedChat',
currentUserId: 'getCurrentUserID',
listLoadingStatus: 'getAllMessagesLoaded',
currentAccountId: 'getCurrentAccountId',
}),
isOpen() {
return this.currentChat?.status === wootConstants.STATUS_TYPE.OPEN;
},
shouldShowLabelSuggestions() {
return (
this.isOpen &&
this.isEnterprise &&
this.isAIIntegrationEnabled &&
!this.messageSentSinceOpened
); );
}, const inboxGetter = useMapGetter('inboxes/getInbox');
inboxId() { const typingUsersListGetter = useMapGetter(
return this.currentChat.inbox_id;
},
inbox() {
return this.$store.getters['inboxes/getInbox'](this.inboxId);
},
typingUsersList() {
const userList = this.$store.getters[
'conversationTypingStatus/getUserList' 'conversationTypingStatus/getUserList'
](this.currentChat.id); );
const isOpen = computed(() => {
return currentChat.value?.status === wootConstants.STATUS_TYPE.OPEN;
});
const shouldShowLabelSuggestions = computed(() => {
return (
isOpen.value &&
isEnterprise &&
isAIIntegrationEnabled.value &&
!messageSentSinceOpened.value
);
});
const inboxId = computed(() => {
return currentChat.value?.inbox_id;
});
const inbox = computed(() => {
return inboxGetter.value(inboxId.value);
});
const {
isAPIInbox,
isAFacebookInbox,
isAWhatsAppChannel,
isAWhatsAppCloudChannel,
isAnInstagramChannel,
is360DialogWhatsAppChannel,
isAnEmailChannel,
} = useInbox();
const inboxHasFeature = feature => {
const channelType = inbox.value?.channel_type;
return INBOX_FEATURE_MAP[feature]?.includes(channelType) ?? false;
};
const typingUsersList = computed(() => {
const userList = typingUsersListGetter.value(currentChat.value.id);
return userList; return userList;
}, });
isAnyoneTyping() {
const userList = this.typingUsersList; const isAnyoneTyping = computed(() => {
const userList = typingUsersList.value;
return userList.length !== 0; return userList.length !== 0;
}, });
typingUserNames() {
const userList = this.typingUsersList; const typingUserNames = computed(() => {
if (this.isAnyoneTyping) { const userList = typingUsersList.value;
if (isAnyoneTyping.value) {
const [i18nKey, params] = getTypingUsersText(userList); const [i18nKey, params] = getTypingUsersText(userList);
return this.$t(i18nKey, params); return t(i18nKey, params);
} }
return ''; return '';
}, });
getMessages() {
const messages = this.currentChat.messages || []; const getMessages = computed(() => {
if (this.isAWhatsAppChannel) { const messages = currentChat.value.messages || [];
if (isAWhatsAppChannel.value) {
return filterDuplicateSourceMessages(messages); return filterDuplicateSourceMessages(messages);
} }
return messages; return messages;
}, });
readMessages() {
return getReadMessages( const unReadMessages = computed(() => {
this.getMessages,
this.currentChat.agent_last_seen_at
);
},
unReadMessages() {
return getUnreadMessages( return getUnreadMessages(
this.getMessages, getMessages.value,
this.currentChat.agent_last_seen_at currentChat.value.agent_last_seen_at
); );
}, });
shouldShowSpinner() {
const shouldShowSpinner = computed(() => {
return ( return (
(this.currentChat && this.currentChat.dataFetched === undefined) || (currentChat.value && currentChat.value.dataFetched === undefined) ||
(!this.listLoadingStatus && this.isLoadingPrevious) (!listLoadingStatus.value && isLoadingPrevious.value)
); );
}, });
// Check there is a instagram inbox exists with the same instagram_id // Check there is a instagram inbox exists with the same instagram_id
hasDuplicateInstagramInbox() { const hasDuplicateInstagramInbox = computed(() => {
const instagramId = this.inbox.instagram_id; const instagramId = inbox.value.instagram_id;
const { additional_attributes: additionalAttributes = {} } = this.inbox; const { additional_attributes: additionalAttributes = {} } = inbox.value;
const instagramInbox = const instagramInbox = instagramInboxByIdGetter.value(instagramId);
this.$store.getters['inboxes/getInstagramInboxByInstagramId'](
instagramId
);
return ( return (
this.inbox.channel_type === INBOX_TYPES.FB && inbox.value.channel_type === INBOX_TYPES.FB &&
additionalAttributes.type === 'instagram_direct_message' && additionalAttributes.type === 'instagram_direct_message' &&
instagramInbox instagramInbox
); );
}, });
replyWindowBannerMessage() { const replyWindowBannerMessage = computed(() => {
if (this.isAWhatsAppChannel) { if (isAWhatsAppChannel.value) {
return this.$t('CONVERSATION.TWILIO_WHATSAPP_CAN_REPLY'); return t('CONVERSATION.TWILIO_WHATSAPP_CAN_REPLY');
} }
if (this.isAPIInbox) { if (isAPIInbox.value) {
const { additional_attributes: additionalAttributes = {} } = this.inbox; const { additional_attributes: additionalAttributes = {} } = inbox.value;
if (additionalAttributes) { if (additionalAttributes) {
const { const {
agent_reply_time_window_message: agentReplyTimeWindowMessage, agent_reply_time_window_message: agentReplyTimeWindowMessage,
@@ -189,264 +196,267 @@ export default {
} = additionalAttributes; } = additionalAttributes;
return ( return (
agentReplyTimeWindowMessage || agentReplyTimeWindowMessage ||
this.$t('CONVERSATION.API_HOURS_WINDOW', { t('CONVERSATION.API_HOURS_WINDOW', {
hours: agentReplyTimeWindow, hours: agentReplyTimeWindow,
}) })
); );
} }
return ''; return '';
} }
return this.$t('CONVERSATION.CANNOT_REPLY'); return t('CONVERSATION.CANNOT_REPLY');
}, });
replyWindowLink() {
if (this.isAFacebookInbox || this.isAnInstagramChannel) { const replyWindowLink = computed(() => {
if (isAFacebookInbox.value || isAnInstagramChannel.value) {
return REPLY_POLICY.FACEBOOK; return REPLY_POLICY.FACEBOOK;
} }
if (this.isAWhatsAppCloudChannel) { if (isAWhatsAppCloudChannel.value) {
return REPLY_POLICY.WHATSAPP_CLOUD; return REPLY_POLICY.WHATSAPP_CLOUD;
} }
if (!this.isAPIInbox) { if (!isAPIInbox.value) {
return REPLY_POLICY.TWILIO_WHATSAPP; return REPLY_POLICY.TWILIO_WHATSAPP;
} }
return ''; return '';
}, });
replyWindowLinkText() {
const replyWindowLinkText = computed(() => {
if ( if (
this.isAWhatsAppChannel || isAWhatsAppChannel.value ||
this.isAFacebookInbox || isAFacebookInbox.value ||
this.isAnInstagramChannel isAnInstagramChannel.value
) { ) {
return this.$t('CONVERSATION.24_HOURS_WINDOW'); return t('CONVERSATION.24_HOURS_WINDOW');
} }
if (!this.isAPIInbox) { if (!isAPIInbox.value) {
return this.$t('CONVERSATION.TWILIO_WHATSAPP_24_HOURS_WINDOW'); return t('CONVERSATION.TWILIO_WHATSAPP_24_HOURS_WINDOW');
} }
return ''; return '';
}, });
unreadMessageCount() {
return this.currentChat.unread_count || 0; const unreadMessageCount = computed(() => {
}, return currentChat.value.unread_count || 0;
unreadMessageLabel() { });
const count =
this.unreadMessageCount > 9 ? '9+' : this.unreadMessageCount; const unreadMessageLabel = computed(() => {
const count = unreadMessageCount.value > 9 ? '9+' : unreadMessageCount.value;
const label = const label =
this.unreadMessageCount > 1 unreadMessageCount.value > 1
? 'CONVERSATION.UNREAD_MESSAGES' ? 'CONVERSATION.UNREAD_MESSAGES'
: 'CONVERSATION.UNREAD_MESSAGE'; : 'CONVERSATION.UNREAD_MESSAGE';
return `${count} ${this.$t(label)}`; return `${count} ${t(label)}`;
}, });
inboxSupportsReplyTo() {
const incoming = this.inboxHasFeature(INBOX_FEATURES.REPLY_TO); const inboxSupportsReplyTo = computed(() => {
const incoming = inboxHasFeature(INBOX_FEATURES.REPLY_TO);
const outgoing = const outgoing =
this.inboxHasFeature(INBOX_FEATURES.REPLY_TO_OUTGOING) && inboxHasFeature(INBOX_FEATURES.REPLY_TO_OUTGOING) &&
!this.is360DialogWhatsAppChannel; !is360DialogWhatsAppChannel.value;
return { incoming, outgoing }; return { incoming, outgoing };
},
},
watch: {
currentChat(newChat, oldChat) {
if (newChat.id === oldChat.id) {
return;
}
this.fetchAllAttachmentsFromCurrentChat();
this.fetchSuggestions();
this.messageSentSinceOpened = false;
},
},
created() {
emitter.on(BUS_EVENTS.SCROLL_TO_MESSAGE, this.onScrollToMessage);
// when a message is sent we set the flag to true this hides the label suggestions,
// until the chat is changed and the flag is reset in the watch for currentChat
emitter.on(BUS_EVENTS.MESSAGE_SENT, () => {
this.messageSentSinceOpened = true;
}); });
},
mounted() { const isLabelSuggestionDismissed = () => {
this.addScrollListener(); return LocalStorage.getFlag(
this.fetchAllAttachmentsFromCurrentChat(); LOCAL_STORAGE_KEYS.DISMISSED_LABEL_SUGGESTIONS,
this.fetchSuggestions(); currentAccountId.value,
}, currentChat.value.id
);
};
unmounted() { const fetchAllAttachmentsFromCurrentChat = () => {
this.removeBusListeners(); store.dispatch('fetchAllAttachments', currentChat.value.id);
this.removeScrollListener(); };
},
methods: { const makeMessagesRead = () => {
async fetchSuggestions() { store.dispatch('markMessagesRead', { id: currentChat.value.id });
};
const setScrollParams = () => {
heightBeforeLoad.value = conversationPanel.value.scrollHeight;
scrollTopBeforeLoad.value = conversationPanel.value.scrollTop;
};
const scrollToBottom = () => {
isProgrammaticScroll.value = true;
let relevantMessages = [];
// label suggestions are not part of the messages list
// so we need to handle them separately
let labelSuggestionsEl =
conversationPanel.value.querySelector('.label-suggestion');
// if there are unread messages, scroll to the first unread message
if (unreadMessageCount.value > 0) {
// capturing only the unread messages
relevantMessages =
conversationPanel.value.querySelectorAll('.message--unread');
} else if (labelSuggestionsEl) {
// when scrolling to the bottom, the label suggestions is below the last message
// so we scroll there if there are no unread messages
// Unread messages always take the highest priority
relevantMessages = [labelSuggestionsEl];
} else {
// if there are no unread messages or label suggestion, scroll to the last message
// capturing last message from the messages list
relevantMessages = Array.from(
conversationPanel.value.querySelectorAll('.message--read')
).slice(-1);
}
conversationPanel.value.scrollTop = calculateScrollTop(
conversationPanel.value.scrollHeight,
instance.proxy.$el.scrollHeight,
relevantMessages
);
};
const fetchPreviousMessages = async (scrollTop = 0) => {
setScrollParams();
const shouldLoadMoreMessages =
currentChat.value.dataFetched === true &&
!listLoadingStatus.value &&
!isLoadingPrevious.value;
if (scrollTop < 100 && !isLoadingPrevious.value && shouldLoadMoreMessages) {
isLoadingPrevious.value = true;
try {
await store.dispatch('fetchPreviousMessages', {
conversationId: currentChat.value.id,
before: currentChat.value.messages[0].id,
});
const heightDifference =
conversationPanel.value.scrollHeight - heightBeforeLoad.value;
conversationPanel.value.scrollTop =
scrollTopBeforeLoad.value + heightDifference;
setScrollParams();
} catch (error) {
// Ignore Error
} finally {
isLoadingPrevious.value = false;
}
}
};
const onScrollToMessage = ({ messageId = '' } = {}) => {
nextTick(() => {
const messageElement = document.getElementById('message' + messageId);
if (messageElement) {
isProgrammaticScroll.value = true;
messageElement.scrollIntoView({ behavior: 'smooth' });
fetchPreviousMessages();
} else {
scrollToBottom();
}
});
makeMessagesRead();
};
const handleScroll = e => {
if (isProgrammaticScroll.value) {
// Reset the flag
isProgrammaticScroll.value = false;
hasUserScrolled.value = false;
} else {
hasUserScrolled.value = true;
}
emitter.emit(BUS_EVENTS.ON_MESSAGE_LIST_SCROLL);
fetchPreviousMessages(e.target.scrollTop);
};
const addScrollListener = () => {
conversationPanel.value = instance.proxy.$el.querySelector(
'.conversation-panel'
);
setScrollParams();
conversationPanel.value.addEventListener('scroll', handleScroll);
nextTick(() => scrollToBottom());
isLoadingPrevious.value = false;
};
const removeScrollListener = () => {
conversationPanel.value.removeEventListener('scroll', handleScroll);
};
const removeBusListeners = () => {
emitter.off(BUS_EVENTS.SCROLL_TO_MESSAGE, onScrollToMessage);
};
const fetchSuggestions = async () => {
// start empty, this ensures that the label suggestions are not shown // start empty, this ensures that the label suggestions are not shown
this.labelSuggestions = []; labelSuggestions.value = [];
if (this.isLabelSuggestionDismissed()) { if (isLabelSuggestionDismissed()) {
return; return;
} }
if (!this.isEnterprise) { if (!isEnterprise) {
return; return;
} }
// Early exit if conversation already has labels - no need to suggest more // Early exit if conversation already has labels - no need to suggest more
const existingLabels = this.currentChat?.labels || []; const existingLabels = currentChat.value?.labels || [];
if (existingLabels.length > 0) return; if (existingLabels.length > 0) return;
// method available in mixin, need to ensure that integrations are present // method available in mixin, need to ensure that integrations are present
await this.fetchIntegrationsIfRequired(); await fetchIntegrationsIfRequired();
if (!this.isLabelSuggestionFeatureEnabled) { if (!isLabelSuggestionFeatureEnabled.value) {
return; return;
} }
this.labelSuggestions = await this.fetchLabelSuggestions({ labelSuggestions.value = await fetchLabelSuggestions({
conversationId: this.currentChat.id, conversationId: currentChat.value.id,
}); });
// once the labels are fetched, we need to scroll to bottom // once the labels are fetched, we need to scroll to bottom
// but we need to wait for the DOM to be updated // but we need to wait for the DOM to be updated
// so we use the nextTick method // so we use the nextTick method
this.$nextTick(() => { nextTick(() => {
// this param is added to route, telling the UI to navigate to the message // this param is added to route, telling the UI to navigate to the message
// it is triggered by the SCROLL_TO_MESSAGE method // it is triggered by the SCROLL_TO_MESSAGE method
// see setActiveChat on ConversationView.vue for more info // see setActiveChat on ConversationView.vue for more info
const { messageId } = this.$route.query; const { messageId } = route.query;
// only trigger the scroll to bottom if the user has not scrolled // only trigger the scroll to bottom if the user has not scrolled
// and there's no active messageId that is selected in view // and there's no active messageId that is selected in view
if (!messageId && !this.hasUserScrolled) { if (!messageId && !hasUserScrolled.value) {
this.scrollToBottom(); scrollToBottom();
} }
}); });
}, };
isLabelSuggestionDismissed() {
return LocalStorage.getFlag(
LOCAL_STORAGE_KEYS.DISMISSED_LABEL_SUGGESTIONS,
this.currentAccountId,
this.currentChat.id
);
},
fetchAllAttachmentsFromCurrentChat() {
this.$store.dispatch('fetchAllAttachments', this.currentChat.id);
},
removeBusListeners() {
emitter.off(BUS_EVENTS.SCROLL_TO_MESSAGE, this.onScrollToMessage);
},
onScrollToMessage({ messageId = '' } = {}) {
this.$nextTick(() => {
const messageElement = document.getElementById('message' + messageId);
if (messageElement) {
this.isProgrammaticScroll = true;
messageElement.scrollIntoView({ behavior: 'smooth' });
this.fetchPreviousMessages();
} else {
this.scrollToBottom();
}
});
this.makeMessagesRead();
},
addScrollListener() {
this.conversationPanel = this.$el.querySelector('.conversation-panel');
this.setScrollParams();
this.conversationPanel.addEventListener('scroll', this.handleScroll);
this.$nextTick(() => this.scrollToBottom());
this.isLoadingPrevious = false;
},
removeScrollListener() {
this.conversationPanel.removeEventListener('scroll', this.handleScroll);
},
scrollToBottom() {
this.isProgrammaticScroll = true;
let relevantMessages = [];
// label suggestions are not part of the messages list const handleMessageRetry = async message => {
// so we need to handle them separately
let labelSuggestions =
this.conversationPanel.querySelector('.label-suggestion');
// if there are unread messages, scroll to the first unread message
if (this.unreadMessageCount > 0) {
// capturing only the unread messages
relevantMessages =
this.conversationPanel.querySelectorAll('.message--unread');
} else if (labelSuggestions) {
// when scrolling to the bottom, the label suggestions is below the last message
// so we scroll there if there are no unread messages
// Unread messages always take the highest priority
relevantMessages = [labelSuggestions];
} else {
// if there are no unread messages or label suggestion, scroll to the last message
// capturing last message from the messages list
relevantMessages = Array.from(
this.conversationPanel.querySelectorAll('.message--read')
).slice(-1);
}
this.conversationPanel.scrollTop = calculateScrollTop(
this.conversationPanel.scrollHeight,
this.$el.scrollHeight,
relevantMessages
);
},
setScrollParams() {
this.heightBeforeLoad = this.conversationPanel.scrollHeight;
this.scrollTopBeforeLoad = this.conversationPanel.scrollTop;
},
async fetchPreviousMessages(scrollTop = 0) {
this.setScrollParams();
const shouldLoadMoreMessages =
this.currentChat.dataFetched === true &&
!this.listLoadingStatus &&
!this.isLoadingPrevious;
if (
scrollTop < 100 &&
!this.isLoadingPrevious &&
shouldLoadMoreMessages
) {
this.isLoadingPrevious = true;
try {
await this.$store.dispatch('fetchPreviousMessages', {
conversationId: this.currentChat.id,
before: this.currentChat.messages[0].id,
});
const heightDifference =
this.conversationPanel.scrollHeight - this.heightBeforeLoad;
this.conversationPanel.scrollTop =
this.scrollTopBeforeLoad + heightDifference;
this.setScrollParams();
} catch (error) {
// Ignore Error
} finally {
this.isLoadingPrevious = false;
}
}
},
handleScroll(e) {
if (this.isProgrammaticScroll) {
// Reset the flag
this.isProgrammaticScroll = false;
this.hasUserScrolled = false;
} else {
this.hasUserScrolled = true;
}
emitter.emit(BUS_EVENTS.ON_MESSAGE_LIST_SCROLL);
this.fetchPreviousMessages(e.target.scrollTop);
},
makeMessagesRead() {
this.$store.dispatch('markMessagesRead', { id: this.currentChat.id });
},
async handleMessageRetry(message) {
if (!message) return; if (!message) return;
const payload = useSnakeCase(message); const payload = useSnakeCase(message);
await this.$store.dispatch('sendMessageWithData', payload); await store.dispatch('sendMessageWithData', payload);
},
},
}; };
watch(currentChat, (newChat, oldChat) => {
if (newChat.id === oldChat.id) {
return;
}
fetchAllAttachmentsFromCurrentChat();
fetchSuggestions();
messageSentSinceOpened.value = false;
});
onMounted(() => {
emitter.on(BUS_EVENTS.SCROLL_TO_MESSAGE, onScrollToMessage);
// when a message is sent we set the flag to true this hides the label suggestions,
// until the chat is changed and the flag is reset in the watch for currentChat
emitter.on(BUS_EVENTS.MESSAGE_SENT, () => {
messageSentSinceOpened.value = true;
});
addScrollListener();
fetchAllAttachmentsFromCurrentChat();
fetchSuggestions();
});
onUnmounted(() => {
removeBusListeners();
removeScrollListener();
});
</script> </script>
<template> <template>
@@ -463,7 +473,7 @@ export default {
v-else-if="hasDuplicateInstagramInbox" v-else-if="hasDuplicateInstagramInbox"
color-scheme="alert" color-scheme="alert"
class="mx-2 mt-2 overflow-hidden rounded-lg" class="mx-2 mt-2 overflow-hidden rounded-lg"
:banner-message="$t('CONVERSATION.OLD_INSTAGRAM_INBOX_REPLY_BANNER')" :banner-message="t('CONVERSATION.OLD_INSTAGRAM_INBOX_REPLY_BANNER')"
/> />
<MessageList <MessageList
ref="conversationPanelRef" ref="conversationPanelRef"