mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-01 19:48:08 +00:00
Merge branch 'develop' into chore/update-rails
This commit is contained in:
@@ -13,6 +13,30 @@ class SearchAPI extends ApiClient {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
contacts({ q }) {
|
||||
return axios.get(`${this.url}/contacts`, {
|
||||
params: {
|
||||
q,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
conversations({ q }) {
|
||||
return axios.get(`${this.url}/conversations`, {
|
||||
params: {
|
||||
q,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
messages({ q }) {
|
||||
return axios.get(`${this.url}/messages`, {
|
||||
params: {
|
||||
q,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new SearchAPI();
|
||||
|
||||
@@ -48,15 +48,17 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
.conversation-priority-mark {
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
background: var(--s-50);
|
||||
border-radius: var(--border-radius-small);
|
||||
color: var(--s-600);
|
||||
display: inline-flex;
|
||||
width: var(--space-snug);
|
||||
height: var(--space-snug);
|
||||
|
||||
&.urgent {
|
||||
background: var(--r-500);
|
||||
color: var(--w-25);
|
||||
background: var(--r-50);
|
||||
color: var(--r-500);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
:title="$t('SEARCH.PLACEHOLDER_KEYBINDING')"
|
||||
:show-close="false"
|
||||
small
|
||||
class="helper-label"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -101,4 +102,8 @@ export default {
|
||||
color: var(--s-400);
|
||||
}
|
||||
}
|
||||
|
||||
.helper-label {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
:title="$t('SEARCH.SECTION.CONTACTS')"
|
||||
:empty="!contacts.length"
|
||||
:query="query"
|
||||
:show-title="showTitle"
|
||||
:is-fetching="isFetching"
|
||||
>
|
||||
<ul class="search-list">
|
||||
<li v-for="contact in contacts" :key="contact.id">
|
||||
@@ -39,6 +41,14 @@ export default {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isFetching: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
:title="$t('SEARCH.SECTION.CONVERSATIONS')"
|
||||
:empty="!conversations.length"
|
||||
:query="query"
|
||||
:show-title="showTitle || isFetching"
|
||||
:is-fetching="isFetching"
|
||||
>
|
||||
<ul class="search-list">
|
||||
<li v-for="conversation in conversations" :key="conversation.id">
|
||||
@@ -37,6 +39,14 @@ export default {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isFetching: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
:title="$t('SEARCH.SECTION.MESSAGES')"
|
||||
:empty="!messages.length"
|
||||
:query="query"
|
||||
:show-title="showTitle"
|
||||
:is-fetching="isFetching"
|
||||
>
|
||||
<ul class="search-list">
|
||||
<li v-for="message in messages" :key="message.id">
|
||||
@@ -45,6 +47,14 @@ export default {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isFetching: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<section class="result-section">
|
||||
<div class="header">
|
||||
<div v-if="showTitle" class="header">
|
||||
<h3 class="text-block-title">{{ title }}</h3>
|
||||
</div>
|
||||
<slot />
|
||||
<div v-if="empty" class="empty">
|
||||
<woot-loading-state v-if="isFetching" :message="'Searching'" />
|
||||
<slot v-else />
|
||||
<div v-if="empty && !isFetching" class="empty">
|
||||
<fluent-icon icon="info" size="16px" class="icon" />
|
||||
<p class="empty-state__text">
|
||||
{{ $t('SEARCH.EMPTY_STATE', { item: titleCase, query }) }}
|
||||
@@ -28,6 +29,14 @@ export default {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
isFetching: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
titleCase() {
|
||||
@@ -39,7 +48,7 @@ export default {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.result-section {
|
||||
margin-bottom: var(--space-normal);
|
||||
margin: var(--space-small) 0;
|
||||
}
|
||||
.search-list {
|
||||
list-style: none;
|
||||
@@ -60,7 +69,7 @@ export default {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-medium) var(--space-normal);
|
||||
margin: 0 var(--space-small);
|
||||
margin: var(--space-small);
|
||||
background: var(--s-25);
|
||||
border-radius: var(--border-radius-medium);
|
||||
.icon {
|
||||
|
||||
@@ -21,39 +21,44 @@
|
||||
/>
|
||||
</header>
|
||||
<div class="search-results">
|
||||
<woot-loading-state v-if="uiFlags.isFetching" :message="'Searching'" />
|
||||
<div v-else>
|
||||
<div v-if="all.length">
|
||||
<search-result-contacts-list
|
||||
v-if="filterContacts"
|
||||
:contacts="contacts"
|
||||
:query="query"
|
||||
/>
|
||||
<search-result-messages-list
|
||||
v-if="filterMessages"
|
||||
:messages="messages"
|
||||
:query="query"
|
||||
/>
|
||||
<search-result-conversations-list
|
||||
v-if="filterConversations"
|
||||
:conversations="conversations"
|
||||
:query="query"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="showEmptySearchResults && !all.length" class="empty">
|
||||
<fluent-icon icon="info" size="16px" class="icon" />
|
||||
<p class="empty-state__text">
|
||||
{{ $t('SEARCH.EMPTY_STATE_FULL', { query }) }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-else class="empty text-center">
|
||||
<p class="text-center margin-bottom-0">
|
||||
<fluent-icon icon="search" size="24px" class="icon" />
|
||||
</p>
|
||||
<p class="empty-state__text">
|
||||
{{ $t('SEARCH.EMPTY_STATE_DEFAULT') }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="all.length">
|
||||
<search-result-contacts-list
|
||||
v-if="filterContacts"
|
||||
:is-fetching="uiFlags.contact.isFetching"
|
||||
:contacts="contacts"
|
||||
:query="query"
|
||||
:show-title="isSelectedTabAll"
|
||||
/>
|
||||
|
||||
<search-result-messages-list
|
||||
v-if="filterMessages"
|
||||
:is-fetching="uiFlags.message.isFetching"
|
||||
:messages="messages"
|
||||
:query="query"
|
||||
:show-title="isSelectedTabAll"
|
||||
/>
|
||||
|
||||
<search-result-conversations-list
|
||||
v-if="filterConversations"
|
||||
:is-fetching="uiFlags.conversation.isFetching"
|
||||
:conversations="conversations"
|
||||
:query="query"
|
||||
:show-title="isSelectedTabAll"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="showEmptySearchResults && !all.length" class="empty">
|
||||
<fluent-icon icon="info" size="16px" class="icon" />
|
||||
<p class="empty-state__text">
|
||||
{{ $t('SEARCH.EMPTY_STATE_FULL', { query }) }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-else class="empty text-center">
|
||||
<p class="text-center margin-bottom-0">
|
||||
<fluent-icon icon="search" size="24px" class="icon" />
|
||||
</p>
|
||||
<p class="empty-state__text">
|
||||
{{ $t('SEARCH.EMPTY_STATE_DEFAULT') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -66,7 +71,6 @@ import SearchTabs from './SearchTabs.vue';
|
||||
import SearchResultConversationsList from './SearchResultConversationsList.vue';
|
||||
import SearchResultMessagesList from './SearchResultMessagesList.vue';
|
||||
import SearchResultContactsList from './SearchResultContactsList.vue';
|
||||
import { isEmptyObject } from 'dashboard/helper/commons.js';
|
||||
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import { mapGetters } from 'vuex';
|
||||
@@ -89,47 +93,40 @@ export default {
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
fullSearchRecords: 'conversationSearch/getFullSearchRecords',
|
||||
contactRecords: 'conversationSearch/getContactRecords',
|
||||
conversationRecords: 'conversationSearch/getConversationRecords',
|
||||
messageRecords: 'conversationSearch/getMessageRecords',
|
||||
uiFlags: 'conversationSearch/getUIFlags',
|
||||
}),
|
||||
contacts() {
|
||||
if (this.fullSearchRecords.contacts) {
|
||||
return this.fullSearchRecords.contacts.map(contact => ({
|
||||
...contact,
|
||||
type: 'contact',
|
||||
}));
|
||||
}
|
||||
return [];
|
||||
return this.contactRecords.map(contact => ({
|
||||
...contact,
|
||||
type: 'contact',
|
||||
}));
|
||||
},
|
||||
conversations() {
|
||||
if (this.fullSearchRecords.conversations) {
|
||||
return this.fullSearchRecords.conversations.map(conversation => ({
|
||||
...conversation,
|
||||
type: 'conversation',
|
||||
}));
|
||||
}
|
||||
return [];
|
||||
return this.conversationRecords.map(conversation => ({
|
||||
...conversation,
|
||||
type: 'conversation',
|
||||
}));
|
||||
},
|
||||
messages() {
|
||||
if (this.fullSearchRecords.messages) {
|
||||
return this.fullSearchRecords.messages.map(message => ({
|
||||
...message,
|
||||
type: 'message',
|
||||
}));
|
||||
}
|
||||
return [];
|
||||
return this.messageRecords.map(message => ({
|
||||
...message,
|
||||
type: 'message',
|
||||
}));
|
||||
},
|
||||
all() {
|
||||
return [...this.contacts, ...this.conversations, ...this.messages];
|
||||
},
|
||||
filterContacts() {
|
||||
return this.selectedTab === 'contacts' || this.selectedTab === 'all';
|
||||
return this.selectedTab === 'contacts' || this.isSelectedTabAll;
|
||||
},
|
||||
filterConversations() {
|
||||
return this.selectedTab === 'conversations' || this.selectedTab === 'all';
|
||||
return this.selectedTab === 'conversations' || this.isSelectedTabAll;
|
||||
},
|
||||
filterMessages() {
|
||||
return this.selectedTab === 'messages' || this.selectedTab === 'all';
|
||||
return this.selectedTab === 'messages' || this.isSelectedTabAll;
|
||||
},
|
||||
totalSearchResultsCount() {
|
||||
return (
|
||||
@@ -162,10 +159,12 @@ export default {
|
||||
},
|
||||
showEmptySearchResults() {
|
||||
return (
|
||||
this.totalSearchResultsCount === 0 &&
|
||||
!isEmptyObject(this.fullSearchRecords)
|
||||
this.totalSearchResultsCount === 0 && this.uiFlags.isSearchCompleted
|
||||
);
|
||||
},
|
||||
isSelectedTabAll() {
|
||||
return this.selectedTab === 'all';
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.query = '';
|
||||
|
||||
@@ -2,9 +2,15 @@ import SearchAPI from '../../api/search';
|
||||
import types from '../mutation-types';
|
||||
export const initialState = {
|
||||
records: [],
|
||||
fullSearchRecords: {},
|
||||
contactRecords: [],
|
||||
conversationRecords: [],
|
||||
messageRecords: [],
|
||||
uiFlags: {
|
||||
isFetching: false,
|
||||
isSearchCompleted: false,
|
||||
contact: { isFetching: false },
|
||||
conversation: { isFetching: false },
|
||||
message: { isFetching: false },
|
||||
},
|
||||
};
|
||||
|
||||
@@ -12,8 +18,14 @@ export const getters = {
|
||||
getConversations(state) {
|
||||
return state.records;
|
||||
},
|
||||
getFullSearchRecords(state) {
|
||||
return state.fullSearchRecords;
|
||||
getContactRecords(state) {
|
||||
return state.contactRecords;
|
||||
},
|
||||
getConversationRecords(state) {
|
||||
return state.conversationRecords;
|
||||
},
|
||||
getMessageRecords(state) {
|
||||
return state.messageRecords;
|
||||
},
|
||||
getUIFlags(state) {
|
||||
return state.uiFlags;
|
||||
@@ -40,23 +52,67 @@ export const actions = {
|
||||
});
|
||||
}
|
||||
},
|
||||
async fullSearch({ commit }, { q }) {
|
||||
commit(types.FULL_SEARCH_SET, []);
|
||||
async fullSearch({ commit, dispatch }, { q }) {
|
||||
if (!q) {
|
||||
return;
|
||||
}
|
||||
commit(types.FULL_SEARCH_SET_UI_FLAG, { isFetching: true });
|
||||
commit(types.FULL_SEARCH_SET_UI_FLAG, {
|
||||
isFetching: true,
|
||||
isSearchCompleted: false,
|
||||
});
|
||||
try {
|
||||
const { data } = await SearchAPI.get({ q });
|
||||
commit(types.FULL_SEARCH_SET, data.payload);
|
||||
dispatch('contactSearch', { q });
|
||||
dispatch('conversationSearch', { q });
|
||||
dispatch('messageSearch', { q });
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
} finally {
|
||||
commit(types.FULL_SEARCH_SET_UI_FLAG, { isFetching: false });
|
||||
commit(types.FULL_SEARCH_SET_UI_FLAG, {
|
||||
isFetching: false,
|
||||
isSearchCompleted: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
async contactSearch({ commit }, { q }) {
|
||||
commit(types.CONTACT_SEARCH_SET, []);
|
||||
commit(types.CONTACT_SEARCH_SET_UI_FLAG, { isFetching: true });
|
||||
try {
|
||||
const { data } = await SearchAPI.contacts({ q });
|
||||
commit(types.CONTACT_SEARCH_SET, data.payload.contacts);
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
} finally {
|
||||
commit(types.CONTACT_SEARCH_SET_UI_FLAG, { isFetching: false });
|
||||
}
|
||||
},
|
||||
async conversationSearch({ commit }, { q }) {
|
||||
commit(types.CONVERSATION_SEARCH_SET, []);
|
||||
commit(types.CONVERSATION_SEARCH_SET_UI_FLAG, { isFetching: true });
|
||||
try {
|
||||
const { data } = await SearchAPI.conversations({ q });
|
||||
commit(types.CONVERSATION_SEARCH_SET, data.payload.conversations);
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
} finally {
|
||||
commit(types.CONVERSATION_SEARCH_SET_UI_FLAG, { isFetching: false });
|
||||
}
|
||||
},
|
||||
async messageSearch({ commit }, { q }) {
|
||||
commit(types.MESSAGE_SEARCH_SET, []);
|
||||
commit(types.MESSAGE_SEARCH_SET_UI_FLAG, { isFetching: true });
|
||||
try {
|
||||
const { data } = await SearchAPI.messages({ q });
|
||||
commit(types.MESSAGE_SEARCH_SET, data.payload.messages);
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
} finally {
|
||||
commit(types.MESSAGE_SEARCH_SET_UI_FLAG, { isFetching: false });
|
||||
}
|
||||
},
|
||||
async clearSearchResults({ commit }) {
|
||||
commit(types.FULL_SEARCH_SET, {});
|
||||
commit(types.MESSAGE_SEARCH_SET, []);
|
||||
commit(types.CONVERSATION_SEARCH_SET, []);
|
||||
commit(types.CONTACT_SEARCH_SET, []);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -64,8 +120,14 @@ export const mutations = {
|
||||
[types.SEARCH_CONVERSATIONS_SET](state, records) {
|
||||
state.records = records;
|
||||
},
|
||||
[types.FULL_SEARCH_SET](state, records) {
|
||||
state.fullSearchRecords = records;
|
||||
[types.CONTACT_SEARCH_SET](state, records) {
|
||||
state.contactRecords = records;
|
||||
},
|
||||
[types.CONVERSATION_SEARCH_SET](state, records) {
|
||||
state.conversationRecords = records;
|
||||
},
|
||||
[types.MESSAGE_SEARCH_SET](state, records) {
|
||||
state.messageRecords = records;
|
||||
},
|
||||
[types.SEARCH_CONVERSATIONS_SET_UI_FLAG](state, uiFlags) {
|
||||
state.uiFlags = { ...state.uiFlags, ...uiFlags };
|
||||
@@ -73,6 +135,15 @@ export const mutations = {
|
||||
[types.FULL_SEARCH_SET_UI_FLAG](state, uiFlags) {
|
||||
state.uiFlags = { ...state.uiFlags, ...uiFlags };
|
||||
},
|
||||
[types.CONTACT_SEARCH_SET_UI_FLAG](state, uiFlags) {
|
||||
state.uiFlags.contact = { ...state.uiFlags.contact, ...uiFlags };
|
||||
},
|
||||
[types.CONVERSATION_SEARCH_SET_UI_FLAG](state, uiFlags) {
|
||||
state.uiFlags.conversation = { ...state.uiFlags.conversation, ...uiFlags };
|
||||
},
|
||||
[types.MESSAGE_SEARCH_SET_UI_FLAG](state, uiFlags) {
|
||||
state.uiFlags.message = { ...state.uiFlags.message, ...uiFlags };
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
|
||||
@@ -269,6 +269,12 @@ export default {
|
||||
|
||||
// Full Search
|
||||
FULL_SEARCH_SET: 'FULL_SEARCH_SET',
|
||||
CONTACT_SEARCH_SET: 'CONTACT_SEARCH_SET',
|
||||
CONTACT_SEARCH_SET_UI_FLAG: 'CONTACT_SEARCH_SET_UI_FLAG',
|
||||
CONVERSATION_SEARCH_SET: 'CONVERSATION_SEARCH_SET',
|
||||
CONVERSATION_SEARCH_SET_UI_FLAG: 'CONVERSATION_SEARCH_SET_UI_FLAG',
|
||||
MESSAGE_SEARCH_SET: 'MESSAGE_SEARCH_SET',
|
||||
MESSAGE_SEARCH_SET_UI_FLAG: 'MESSAGE_SEARCH_SET_UI_FLAG',
|
||||
FULL_SEARCH_SET_UI_FLAG: 'FULL_SEARCH_SET_UI_FLAG',
|
||||
SET_CONVERSATION_PARTICIPANTS_UI_FLAG:
|
||||
'SET_CONVERSATION_PARTICIPANTS_UI_FLAG',
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
} from 'shared/helpers/AudioNotificationHelper';
|
||||
import { isFlatWidgetStyle } from './settingsHelper';
|
||||
import { popoutChatWindow } from '../widget/helpers/popoutHelper';
|
||||
import addHours from 'date-fns/addHours';
|
||||
|
||||
const updateAuthCookie = cookieContent =>
|
||||
Cookies.set('cw_conversation', cookieContent, {
|
||||
@@ -43,6 +44,14 @@ const updateAuthCookie = cookieContent =>
|
||||
sameSite: 'Lax',
|
||||
});
|
||||
|
||||
const updateCampaignReadStatus = () => {
|
||||
const expireBy = addHours(new Date(), 1);
|
||||
Cookies.set('cw_snooze_campaigns_till', Number(expireBy), {
|
||||
expires: expireBy,
|
||||
sameSite: 'Lax',
|
||||
});
|
||||
};
|
||||
|
||||
export const IFrameHelper = {
|
||||
getUrl({ baseUrl, websiteToken }) {
|
||||
return `${baseUrl}/widget?website_token=${websiteToken}`;
|
||||
@@ -147,6 +156,7 @@ export const IFrameHelper = {
|
||||
loaded: message => {
|
||||
updateAuthCookie(message.config.authToken);
|
||||
window.$chatwoot.hasLoaded = true;
|
||||
const campaignsSnoozedTill = Cookies.get('cw_snooze_campaigns_till');
|
||||
IFrameHelper.sendMessage('config-set', {
|
||||
locale: window.$chatwoot.locale,
|
||||
position: window.$chatwoot.position,
|
||||
@@ -154,6 +164,7 @@ export const IFrameHelper = {
|
||||
showPopoutButton: window.$chatwoot.showPopoutButton,
|
||||
widgetStyle: window.$chatwoot.widgetStyle,
|
||||
darkMode: window.$chatwoot.darkMode,
|
||||
campaignsSnoozedTill,
|
||||
});
|
||||
IFrameHelper.onLoad({
|
||||
widgetColor: message.config.channelConfig.widgetColor,
|
||||
@@ -192,6 +203,10 @@ export const IFrameHelper = {
|
||||
updateAuthCookie(widgetAuthToken);
|
||||
},
|
||||
|
||||
setCampaignReadOn() {
|
||||
updateCampaignReadStatus();
|
||||
},
|
||||
|
||||
toggleBubble: state => {
|
||||
let bubbleState = {};
|
||||
if (state === 'open') {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import { setHeader } from 'widget/helpers/axios';
|
||||
import addHours from 'date-fns/addHours';
|
||||
import { IFrameHelper, RNHelper } from 'widget/helpers/utils';
|
||||
import configMixin from './mixins/configMixin';
|
||||
import availabilityMixin from 'widget/mixins/availability';
|
||||
@@ -50,6 +51,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
isMobile: false,
|
||||
campaignsSnoozedTill: undefined,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -179,11 +181,19 @@ export default {
|
||||
this.executeCampaign({ campaignId, websiteToken, customAttributes });
|
||||
this.replaceRoute('messages');
|
||||
});
|
||||
bus.$on('snooze-campaigns', () => {
|
||||
const expireBy = addHours(new Date(), 1);
|
||||
this.campaignsSnoozedTill = Number(expireBy);
|
||||
});
|
||||
},
|
||||
setCampaignView() {
|
||||
const { messageCount, activeCampaign } = this;
|
||||
const shouldSnoozeCampaign =
|
||||
this.campaignsSnoozedTill && this.campaignsSnoozedTill > Date.now();
|
||||
const isCampaignReadyToExecute =
|
||||
!isEmptyObject(activeCampaign) && !messageCount;
|
||||
!isEmptyObject(activeCampaign) &&
|
||||
!messageCount &&
|
||||
!shouldSnoozeCampaign;
|
||||
if (this.isIFrame && isCampaignReadyToExecute) {
|
||||
this.replaceRoute('campaigns').then(() => {
|
||||
this.setIframeHeight(true);
|
||||
@@ -243,6 +253,7 @@ export default {
|
||||
this.fetchAvailableAgents(websiteToken);
|
||||
this.setAppConfig(message);
|
||||
this.$store.dispatch('contacts/get');
|
||||
this.setCampaignReadData(message.campaignsSnoozedTill);
|
||||
} else if (message.event === 'widget-visible') {
|
||||
this.scrollConversationToBottom();
|
||||
} else if (message.event === 'change-url') {
|
||||
@@ -320,6 +331,11 @@ export default {
|
||||
sendRNWebViewLoadedEvent() {
|
||||
RNHelper.sendMessage(loadedEventConfig());
|
||||
},
|
||||
setCampaignReadData(snoozedTill) {
|
||||
if (snoozedTill) {
|
||||
this.campaignsSnoozedTill = Number(snoozedTill);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -45,7 +45,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IFrameHelper } from 'widget/helpers/utils';
|
||||
import { mapGetters } from 'vuex';
|
||||
import configMixin from '../mixins/configMixin';
|
||||
import { ON_UNREAD_MESSAGE_CLICK } from '../constants/widgetBusEvents';
|
||||
@@ -84,9 +83,7 @@ export default {
|
||||
bus.$emit(ON_UNREAD_MESSAGE_CLICK);
|
||||
},
|
||||
closeFullView() {
|
||||
if (IFrameHelper.isIFrame()) {
|
||||
IFrameHelper.sendMessage({ event: 'toggleBubble' });
|
||||
}
|
||||
this.$emit('close');
|
||||
},
|
||||
getMessageContent(message) {
|
||||
const { attachments, content } = message;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<template>
|
||||
<unread-message-list :messages="messages" />
|
||||
<unread-message-list :messages="messages" @close="closeFullView" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { IFrameHelper } from 'widget/helpers/utils';
|
||||
import UnreadMessageList from '../components/UnreadMessageList.vue';
|
||||
|
||||
export default {
|
||||
@@ -24,5 +25,16 @@ export default {
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
closeFullView() {
|
||||
if (IFrameHelper.isIFrame()) {
|
||||
IFrameHelper.sendMessage({
|
||||
event: 'setCampaignReadOn',
|
||||
});
|
||||
IFrameHelper.sendMessage({ event: 'toggleBubble' });
|
||||
bus.$emit('snooze-campaigns');
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<template>
|
||||
<unread-message-list :messages="messages" />
|
||||
<unread-message-list :messages="messages" @close="closeFullView" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { IFrameHelper } from 'widget/helpers/utils';
|
||||
import UnreadMessageList from '../components/UnreadMessageList.vue';
|
||||
|
||||
export default {
|
||||
@@ -16,5 +17,12 @@ export default {
|
||||
messages: 'conversation/getUnreadTextMessages',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
closeFullView() {
|
||||
if (IFrameHelper.isIFrame()) {
|
||||
IFrameHelper.sendMessage({ event: 'toggleBubble' });
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -221,14 +221,14 @@ class Conversation < ApplicationRecord
|
||||
end
|
||||
|
||||
def notify_conversation_updation
|
||||
return unless previous_changes.keys.present? && whitelisted_keys?
|
||||
return unless previous_changes.keys.present? && allowed_keys?
|
||||
|
||||
dispatcher_dispatch(CONVERSATION_UPDATED, previous_changes)
|
||||
end
|
||||
|
||||
def whitelisted_keys?
|
||||
def allowed_keys?
|
||||
(
|
||||
(previous_changes.keys & %w[team_id assignee_id status snoozed_until custom_attributes label_list first_reply_created_at]).present? ||
|
||||
(previous_changes.keys & %w[team_id assignee_id status snoozed_until custom_attributes label_list first_reply_created_at priority]).present? ||
|
||||
(previous_changes['additional_attributes'].present? && (previous_changes['additional_attributes'][1].keys & %w[conversation_language]).present?)
|
||||
)
|
||||
end
|
||||
|
||||
@@ -49,7 +49,7 @@ class Message < ApplicationRecord
|
||||
'namespace': { 'type': 'string' },
|
||||
'processed_params': { 'type': 'object' }
|
||||
},
|
||||
'required': %w[name category language namespace processed_params]
|
||||
'required': %w[name]
|
||||
}
|
||||
}
|
||||
}.to_json.freeze
|
||||
|
||||
@@ -15,6 +15,7 @@ class Conversations::EventDataPresenter < SimpleDelegator
|
||||
snoozed_until: snoozed_until,
|
||||
unread_count: unread_incoming_messages.count,
|
||||
first_reply_created_at: first_reply_created_at,
|
||||
priority: priority,
|
||||
**push_timestamps
|
||||
}
|
||||
end
|
||||
|
||||
@@ -518,6 +518,7 @@ RSpec.describe Conversation, type: :model do
|
||||
contact_last_seen_at: conversation.contact_last_seen_at.to_i,
|
||||
agent_last_seen_at: conversation.agent_last_seen_at.to_i,
|
||||
created_at: conversation.created_at.to_i,
|
||||
priority: nil,
|
||||
unread_count: 0
|
||||
}
|
||||
end
|
||||
|
||||
@@ -31,6 +31,7 @@ RSpec.describe Conversations::EventDataPresenter do
|
||||
contact_last_seen_at: conversation.contact_last_seen_at.to_i,
|
||||
agent_last_seen_at: conversation.agent_last_seen_at.to_i,
|
||||
created_at: conversation.created_at.to_i,
|
||||
priority: nil,
|
||||
unread_count: 0
|
||||
}
|
||||
end
|
||||
|
||||
@@ -382,7 +382,7 @@
|
||||
$ref: ./application/teams/update.yml
|
||||
delete:
|
||||
$ref: ./application/teams/delete.yml
|
||||
/accounts/{account_id}/teams/{team_id}/team_members:
|
||||
/api/v1/accounts/{account_id}/teams/{team_id}/team_members:
|
||||
parameters:
|
||||
- $ref: '#/parameters/account_id'
|
||||
- $ref: '#/parameters/team_id'
|
||||
|
||||
@@ -3980,7 +3980,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/accounts/{account_id}/teams/{team_id}/team_members": {
|
||||
"/api/v1/accounts/{account_id}/teams/{team_id}/team_members": {
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/parameters/account_id"
|
||||
|
||||
Reference in New Issue
Block a user