Co-authored-by: Kaj Oudshoorn <kaj@milvum.com> Co-authored-by: Pranav Raj S <pranav@chatwoot.com> Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
This commit is contained in:
parent
2ddd508aee
commit
af1d8c0ee5
11 changed files with 109 additions and 23 deletions
|
@ -43,7 +43,7 @@ $woot-logo-padding: $space-large $space-two;
|
||||||
// Colors
|
// Colors
|
||||||
$color-woot: #1f93ff;
|
$color-woot: #1f93ff;
|
||||||
$color-gray: #6e6f73;
|
$color-gray: #6e6f73;
|
||||||
$color-light-gray: #999a9b;
|
$color-light-gray: #747677;
|
||||||
$color-border: #e0e6ed;
|
$color-border: #e0e6ed;
|
||||||
$color-border-light: #f0f4f5;
|
$color-border-light: #f0f4f5;
|
||||||
$color-background: #f4f6fb;
|
$color-background: #f4f6fb;
|
||||||
|
|
|
@ -142,6 +142,7 @@ export const IFrameHelper = {
|
||||||
},
|
},
|
||||||
|
|
||||||
onBubbleToggle: isOpen => {
|
onBubbleToggle: isOpen => {
|
||||||
|
IFrameHelper.sendMessage('toggle-open', { isOpen });
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
IFrameHelper.events.resetUnreadMode();
|
IFrameHelper.events.resetUnreadMode();
|
||||||
} else {
|
} else {
|
||||||
|
@ -194,6 +195,10 @@ export const IFrameHelper = {
|
||||||
const holderEl = document.querySelector('.woot-widget-holder');
|
const holderEl = document.querySelector('.woot-widget-holder');
|
||||||
removeClass(holderEl, 'has-unread-view');
|
removeClass(holderEl, 'has-unread-view');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
closeChat: () => {
|
||||||
|
onBubbleClick({ toggleValue: false });
|
||||||
|
},
|
||||||
},
|
},
|
||||||
pushEvent: eventName => {
|
pushEvent: eventName => {
|
||||||
IFrameHelper.sendMessage('push-event', { eventName });
|
IFrameHelper.sendMessage('push-event', { eventName });
|
||||||
|
|
|
@ -9,8 +9,8 @@ export const body = document.getElementsByTagName('body')[0];
|
||||||
export const widgetHolder = document.createElement('div');
|
export const widgetHolder = document.createElement('div');
|
||||||
|
|
||||||
export const bubbleHolder = document.createElement('div');
|
export const bubbleHolder = document.createElement('div');
|
||||||
export const chatBubble = document.createElement('div');
|
export const chatBubble = document.createElement('button');
|
||||||
export const closeBubble = document.createElement('div');
|
export const closeBubble = document.createElement('button');
|
||||||
export const notificationBubble = document.createElement('span');
|
export const notificationBubble = document.createElement('span');
|
||||||
|
|
||||||
export const getBubbleView = type =>
|
export const getBubbleView = type =>
|
||||||
|
@ -64,6 +64,10 @@ export const onBubbleClick = (props = {}) => {
|
||||||
toggleClass(closeBubble, 'woot--hide');
|
toggleClass(closeBubble, 'woot--hide');
|
||||||
toggleClass(widgetHolder, 'woot--hide');
|
toggleClass(widgetHolder, 'woot--hide');
|
||||||
IFrameHelper.events.onBubbleToggle(newIsOpen);
|
IFrameHelper.events.onBubbleToggle(newIsOpen);
|
||||||
|
|
||||||
|
if (!newIsOpen) {
|
||||||
|
chatBubble.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,9 @@ export const SDK_CSS = `.woot-widget-holder {
|
||||||
.woot-widget-bubble {
|
.woot-widget-bubble {
|
||||||
background: #1f93ff;
|
background: #1f93ff;
|
||||||
border-radius: 100px !important;
|
border-radius: 100px !important;
|
||||||
|
border-width: 0px;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
|
padding: 0px;
|
||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, .16) !important;
|
box-shadow: 0 8px 24px rgba(0, 0, 0, .16) !important;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: 64px !important;
|
height: 64px !important;
|
||||||
|
@ -40,6 +42,7 @@ export const SDK_CSS = `.woot-widget-holder {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 48px !important;
|
height: 48px !important;
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.woot-widget-bubble.woot-widget--expanded div {
|
.woot-widget-bubble.woot-widget--expanded div {
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters, mapActions } from 'vuex';
|
import { mapGetters, mapActions, mapMutations } from 'vuex';
|
||||||
import { setHeader } from 'widget/helpers/axios';
|
import { setHeader } from 'widget/helpers/axios';
|
||||||
import { IFrameHelper, RNHelper } from 'widget/helpers/utils';
|
import { IFrameHelper, RNHelper } from 'widget/helpers/utils';
|
||||||
import Router from './views/Router';
|
import Router from './views/Router';
|
||||||
|
@ -97,6 +97,7 @@ export default {
|
||||||
...mapActions('conversation', ['fetchOldConversations', 'setUserLastSeen']),
|
...mapActions('conversation', ['fetchOldConversations', 'setUserLastSeen']),
|
||||||
...mapActions('campaign', ['initCampaigns', 'executeCampaign']),
|
...mapActions('campaign', ['initCampaigns', 'executeCampaign']),
|
||||||
...mapActions('agent', ['fetchAvailableAgents']),
|
...mapActions('agent', ['fetchAvailableAgents']),
|
||||||
|
...mapMutations('events', ['toggleOpen']),
|
||||||
scrollConversationToBottom() {
|
scrollConversationToBottom() {
|
||||||
const container = this.$el.querySelector('.conversation-wrap');
|
const container = this.$el.querySelector('.conversation-wrap');
|
||||||
container.scrollTop = container.scrollHeight;
|
container.scrollTop = container.scrollHeight;
|
||||||
|
@ -249,6 +250,8 @@ export default {
|
||||||
} else if (message.event === 'unset-unread-view') {
|
} else if (message.event === 'unset-unread-view') {
|
||||||
this.showUnreadView = false;
|
this.showUnreadView = false;
|
||||||
this.showCampaignView = false;
|
this.showCampaignView = false;
|
||||||
|
} else if (message.event === 'toggle-open') {
|
||||||
|
this.toggleOpen();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -74,3 +74,8 @@ $color-shadow-outline: rgba(66, 153, 225, 0.5);
|
||||||
@mixin shadow-none {
|
@mixin shadow-none {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin button-size {
|
||||||
|
min-height: $space-large;
|
||||||
|
min-width: $space-large;
|
||||||
|
}
|
||||||
|
|
|
@ -68,13 +68,19 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import '~widget/assets/scss/variables.scss';
|
@import '~widget/assets/scss/variables.scss';
|
||||||
|
@import '~widget/assets/scss/mixins.scss';
|
||||||
|
|
||||||
.attachment-button {
|
.attachment-button {
|
||||||
|
@include button-size;
|
||||||
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 0;
|
border: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
i {
|
i {
|
||||||
font-size: $font-size-large;
|
font-size: $font-size-large;
|
||||||
|
|
|
@ -124,7 +124,6 @@ export default {
|
||||||
.footer {
|
.footer {
|
||||||
background: $color-white;
|
background: $color-white;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: $space-small $space-slab;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
@include shadow-big;
|
@include shadow-big;
|
||||||
|
|
|
@ -1,27 +1,42 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="chat-message--input">
|
<div
|
||||||
|
class="chat-message--input"
|
||||||
|
:class="{ 'is-focused': isFocused }"
|
||||||
|
@keydown.esc="hideEmojiPicker"
|
||||||
|
>
|
||||||
<resizable-text-area
|
<resizable-text-area
|
||||||
|
id="chat-input"
|
||||||
|
ref="chatInput"
|
||||||
v-model="userInput"
|
v-model="userInput"
|
||||||
|
:aria-label="$t('CHAT_PLACEHOLDER')"
|
||||||
:placeholder="$t('CHAT_PLACEHOLDER')"
|
:placeholder="$t('CHAT_PLACEHOLDER')"
|
||||||
class="form-input user-message-input"
|
class="form-input user-message-input"
|
||||||
@typing-off="onTypingOff"
|
@typing-off="onTypingOff"
|
||||||
@typing-on="onTypingOn"
|
@typing-on="onTypingOn"
|
||||||
|
@focus="onFocus"
|
||||||
|
@blur="onBlur"
|
||||||
/>
|
/>
|
||||||
<div class="button-wrap">
|
<div class="button-wrap">
|
||||||
<chat-attachment-button
|
<chat-attachment-button
|
||||||
v-if="showAttachment"
|
v-if="showAttachment"
|
||||||
:on-attach="onSendAttachment"
|
:on-attach="onSendAttachment"
|
||||||
/>
|
/>
|
||||||
|
<button
|
||||||
|
v-if="hasEmojiPickerEnabled"
|
||||||
|
class="emoji-toggle"
|
||||||
|
aria-label="Emoji picker"
|
||||||
|
@click="toggleEmojiPicker()"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="icon ion-happy-outline"
|
||||||
|
:class="{ active: showEmojiPicker }"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
<emoji-input
|
<emoji-input
|
||||||
v-if="showEmojiPicker"
|
v-if="showEmojiPicker"
|
||||||
v-on-clickaway="hideEmojiPicker"
|
v-on-clickaway="hideEmojiPicker"
|
||||||
:on-click="emojiOnClick"
|
:on-click="emojiOnClick"
|
||||||
/>
|
@keydown.esc="hideEmojiPicker"
|
||||||
<i
|
|
||||||
v-if="hasEmojiPickerEnabled"
|
|
||||||
class="emoji-toggle icon ion-happy-outline"
|
|
||||||
:class="{ active: showEmojiPicker }"
|
|
||||||
@click="toggleEmojiPicker()"
|
|
||||||
/>
|
/>
|
||||||
<chat-send-button
|
<chat-send-button
|
||||||
v-if="showSendButton"
|
v-if="showSendButton"
|
||||||
|
@ -65,6 +80,7 @@ export default {
|
||||||
return {
|
return {
|
||||||
userInput: '',
|
userInput: '',
|
||||||
showEmojiPicker: false,
|
showEmojiPicker: false,
|
||||||
|
isFocused: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -78,21 +94,40 @@ export default {
|
||||||
showSendButton() {
|
showSendButton() {
|
||||||
return this.userInput.length > 0;
|
return this.userInput.length > 0;
|
||||||
},
|
},
|
||||||
|
isOpen() {
|
||||||
|
return this.$store.state.events.isOpen;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
isOpen(isOpen) {
|
||||||
|
if (isOpen) {
|
||||||
|
this.focusInput();
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
destroyed() {
|
destroyed() {
|
||||||
document.removeEventListener('keypress', this.handleEnterKeyPress);
|
document.removeEventListener('keypress', this.handleEnterKeyPress);
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
document.addEventListener('keypress', this.handleEnterKeyPress);
|
document.addEventListener('keypress', this.handleEnterKeyPress);
|
||||||
|
if (this.isOpen) {
|
||||||
|
this.focusInput();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
onBlur() {
|
||||||
|
this.isFocused = false;
|
||||||
|
},
|
||||||
|
onFocus() {
|
||||||
|
this.isFocused = true;
|
||||||
|
},
|
||||||
handleButtonClick() {
|
handleButtonClick() {
|
||||||
if (this.userInput && this.userInput.trim()) {
|
if (this.userInput && this.userInput.trim()) {
|
||||||
this.onSendMessage(this.userInput);
|
this.onSendMessage(this.userInput);
|
||||||
}
|
}
|
||||||
this.userInput = '';
|
this.userInput = '';
|
||||||
|
this.focusInput();
|
||||||
},
|
},
|
||||||
handleEnterKeyPress(e) {
|
handleEnterKeyPress(e) {
|
||||||
if (e.keyCode === 13 && !e.shiftKey) {
|
if (e.keyCode === 13 && !e.shiftKey) {
|
||||||
|
@ -103,8 +138,9 @@ export default {
|
||||||
toggleEmojiPicker() {
|
toggleEmojiPicker() {
|
||||||
this.showEmojiPicker = !this.showEmojiPicker;
|
this.showEmojiPicker = !this.showEmojiPicker;
|
||||||
},
|
},
|
||||||
hideEmojiPicker() {
|
hideEmojiPicker(e) {
|
||||||
if (this.showEmojiPicker) {
|
if (this.showEmojiPicker) {
|
||||||
|
e.stopPropagation();
|
||||||
this.toggleEmojiPicker();
|
this.toggleEmojiPicker();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -120,22 +156,33 @@ export default {
|
||||||
toggleTyping(typingStatus) {
|
toggleTyping(typingStatus) {
|
||||||
this.$store.dispatch('conversation/toggleUserTyping', { typingStatus });
|
this.$store.dispatch('conversation/toggleUserTyping', { typingStatus });
|
||||||
},
|
},
|
||||||
|
focusInput() {
|
||||||
|
this.$refs.chatInput.focus();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import '~widget/assets/scss/variables.scss';
|
@import '~widget/assets/scss/variables.scss';
|
||||||
|
@import '~widget/assets/scss/mixins.scss';
|
||||||
|
|
||||||
.chat-message--input {
|
.chat-message--input {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
padding: 0 $space-small 0 $space-slab;
|
||||||
|
border-radius: 7px;
|
||||||
|
|
||||||
|
&.is-focused {
|
||||||
|
box-shadow: 0 0 0 1px $color-woot, 0 0 2px 3px $color-primary-light;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-toggle {
|
.emoji-toggle {
|
||||||
|
@include button-size;
|
||||||
|
|
||||||
font-size: $font-size-large;
|
font-size: $font-size-large;
|
||||||
color: $color-gray;
|
color: $color-gray;
|
||||||
padding-right: $space-smaller;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,13 +190,10 @@ export default {
|
||||||
right: $space-one;
|
right: $space-one;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-uploads {
|
|
||||||
margin-right: $space-small;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-wrap {
|
.button-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding-left: $space-small;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-message-input {
|
.user-message-input {
|
||||||
|
@ -158,6 +202,9 @@ export default {
|
||||||
min-height: $space-large;
|
min-height: $space-large;
|
||||||
max-height: 2.4 * $space-mega;
|
max-height: 2.4 * $space-mega;
|
||||||
resize: none;
|
resize: none;
|
||||||
|
padding: 0;
|
||||||
padding-top: $space-small;
|
padding-top: $space-small;
|
||||||
|
margin-top: $space-small;
|
||||||
|
margin-bottom: $space-small;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import events from 'widget/api/events';
|
import events from 'widget/api/events';
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
isOpen: false,
|
||||||
|
}
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
create: async (_, { name }) => {
|
create: async (_, { name }) => {
|
||||||
try {
|
try {
|
||||||
|
@ -10,10 +14,16 @@ const actions = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
toggleOpen($state) {
|
||||||
|
$state.isOpen = !$state.isOpen;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: {},
|
state,
|
||||||
getters: {},
|
getters: {},
|
||||||
actions,
|
actions,
|
||||||
mutations: {},
|
mutations,
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
>
|
>
|
||||||
<spinner size="" />
|
<spinner size="" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="home">
|
<div v-else class="home" @keydown.esc="closeChat">
|
||||||
<div
|
<div
|
||||||
class="header-wrap bg-white"
|
class="header-wrap bg-white"
|
||||||
:class="{ expanded: !isHeaderCollapsed, collapsed: isHeaderCollapsed }"
|
:class="{ expanded: !isHeaderCollapsed, collapsed: isHeaderCollapsed }"
|
||||||
|
@ -74,6 +74,7 @@ import ChatFooter from 'widget/components/ChatFooter.vue';
|
||||||
import ChatHeaderExpanded from 'widget/components/ChatHeaderExpanded.vue';
|
import ChatHeaderExpanded from 'widget/components/ChatHeaderExpanded.vue';
|
||||||
import ChatHeader from 'widget/components/ChatHeader.vue';
|
import ChatHeader from 'widget/components/ChatHeader.vue';
|
||||||
import ConversationWrap from 'widget/components/ConversationWrap.vue';
|
import ConversationWrap from 'widget/components/ConversationWrap.vue';
|
||||||
|
import { IFrameHelper } from 'widget/helpers/utils';
|
||||||
import configMixin from '../mixins/configMixin';
|
import configMixin from '../mixins/configMixin';
|
||||||
import TeamAvailability from 'widget/components/TeamAvailability';
|
import TeamAvailability from 'widget/components/TeamAvailability';
|
||||||
import Spinner from 'shared/components/Spinner.vue';
|
import Spinner from 'shared/components/Spinner.vue';
|
||||||
|
@ -166,6 +167,9 @@ export default {
|
||||||
startConversation() {
|
startConversation() {
|
||||||
this.isOnCollapsedView = !this.isOnCollapsedView;
|
this.isOnCollapsedView = !this.isOnCollapsedView;
|
||||||
},
|
},
|
||||||
|
closeChat() {
|
||||||
|
IFrameHelper.sendMessage({ event: 'closeChat' });
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -227,7 +231,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-wrap {
|
.input-wrap {
|
||||||
padding: 0 $space-normal;
|
padding: 0 $space-two;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue