feat: Add a popout option on webwidget (#1174)
* feat: Add a popout option on webwidget
This commit is contained in:
parent
ce13efd273
commit
45cd949c40
17 changed files with 347 additions and 127 deletions
|
@ -14,6 +14,7 @@ const runSDK = ({ baseUrl, websiteToken }) => {
|
||||||
locale: chatwootSettings.locale,
|
locale: chatwootSettings.locale,
|
||||||
type: getBubbleView(chatwootSettings.type),
|
type: getBubbleView(chatwootSettings.type),
|
||||||
launcherTitle: chatwootSettings.launcherTitle || '',
|
launcherTitle: chatwootSettings.launcherTitle || '',
|
||||||
|
showPopoutButton: chatwootSettings.showPopoutButton || false,
|
||||||
|
|
||||||
toggle() {
|
toggle() {
|
||||||
IFrameHelper.events.toggleBubble();
|
IFrameHelper.events.toggleBubble();
|
||||||
|
|
|
@ -99,6 +99,7 @@ export const IFrameHelper = {
|
||||||
locale: window.$chatwoot.locale,
|
locale: window.$chatwoot.locale,
|
||||||
position: window.$chatwoot.position,
|
position: window.$chatwoot.position,
|
||||||
hideMessageBubble: window.$chatwoot.hideMessageBubble,
|
hideMessageBubble: window.$chatwoot.hideMessageBubble,
|
||||||
|
showPopoutButton: window.$chatwoot.showPopoutButton,
|
||||||
});
|
});
|
||||||
IFrameHelper.onLoad({
|
IFrameHelper.onLoad({
|
||||||
widgetColor: message.config.channelConfig.widgetColor,
|
widgetColor: message.config.channelConfig.widgetColor,
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
:unread-message-count="unreadMessageCount"
|
:unread-message-count="unreadMessageCount"
|
||||||
:is-left-aligned="isLeftAligned"
|
:is-left-aligned="isLeftAligned"
|
||||||
:hide-message-bubble="hideMessageBubble"
|
:hide-message-bubble="hideMessageBubble"
|
||||||
|
:show-popout-button="showPopoutButton"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -21,6 +22,7 @@ import { setHeader } from 'widget/helpers/axios';
|
||||||
import { IFrameHelper } from 'widget/helpers/utils';
|
import { IFrameHelper } from 'widget/helpers/utils';
|
||||||
|
|
||||||
import Router from './views/Router';
|
import Router from './views/Router';
|
||||||
|
import { getLocale } from './helpers/urlParamsHelper';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
|
@ -33,6 +35,7 @@ export default {
|
||||||
isMobile: false,
|
isMobile: false,
|
||||||
hideMessageBubble: false,
|
hideMessageBubble: false,
|
||||||
widgetPosition: 'right',
|
widgetPosition: 'right',
|
||||||
|
showPopoutButton: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -49,74 +52,25 @@ export default {
|
||||||
const isLeft = this.widgetPosition === 'left';
|
const isLeft = this.widgetPosition === 'left';
|
||||||
return isLeft;
|
return isLeft;
|
||||||
},
|
},
|
||||||
|
isIFrame() {
|
||||||
|
return IFrameHelper.isIFrame();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
const { websiteToken, locale } = window.chatwootWebChannel;
|
const { websiteToken, locale } = window.chatwootWebChannel;
|
||||||
this.setLocale(locale);
|
this.setLocale(locale);
|
||||||
|
if (this.isIFrame) {
|
||||||
if (IFrameHelper.isIFrame()) {
|
this.registerListeners();
|
||||||
IFrameHelper.sendMessage({
|
this.sendLoadedEvent();
|
||||||
event: 'loaded',
|
|
||||||
config: {
|
|
||||||
authToken: window.authToken,
|
|
||||||
channelConfig: window.chatwootWebChannel,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
setHeader('X-Auth-Token', window.authToken);
|
setHeader('X-Auth-Token', window.authToken);
|
||||||
|
} else {
|
||||||
|
setHeader('X-Auth-Token', window.authToken);
|
||||||
|
this.fetchOldConversations();
|
||||||
|
this.fetchAvailableAgents(websiteToken);
|
||||||
|
this.setLocale(getLocale(window.location.search));
|
||||||
}
|
}
|
||||||
this.setWidgetColor(window.chatwootWebChannel);
|
|
||||||
|
|
||||||
window.addEventListener('message', e => {
|
|
||||||
const wootPrefix = 'chatwoot-widget:';
|
|
||||||
const isDataNotString = typeof e.data !== 'string';
|
|
||||||
const isNotFromWoot = isDataNotString || e.data.indexOf(wootPrefix) !== 0;
|
|
||||||
|
|
||||||
if (isNotFromWoot) return;
|
|
||||||
|
|
||||||
const message = JSON.parse(e.data.replace(wootPrefix, ''));
|
|
||||||
if (message.event === 'config-set') {
|
|
||||||
this.setLocale(message.locale);
|
|
||||||
this.setBubbleLabel();
|
|
||||||
this.setPosition(message.position);
|
|
||||||
this.fetchOldConversations().then(() => {
|
|
||||||
this.setUnreadView();
|
|
||||||
});
|
|
||||||
this.fetchAvailableAgents(websiteToken);
|
|
||||||
this.setHideMessageBubble(message.hideMessageBubble);
|
|
||||||
} else if (message.event === 'widget-visible') {
|
|
||||||
this.scrollConversationToBottom();
|
|
||||||
} else if (message.event === 'set-current-url') {
|
|
||||||
window.refererURL = message.refererURL;
|
|
||||||
} else if (message.event === 'toggle-close-button') {
|
|
||||||
this.isMobile = message.showClose;
|
|
||||||
} else if (message.event === 'push-event') {
|
|
||||||
this.createWidgetEvents(message);
|
|
||||||
} else if (message.event === 'set-label') {
|
|
||||||
this.$store.dispatch('conversationLabels/create', message.label);
|
|
||||||
} else if (message.event === 'remove-label') {
|
|
||||||
this.$store.dispatch('conversationLabels/destroy', message.label);
|
|
||||||
} else if (message.event === 'set-user') {
|
|
||||||
this.$store.dispatch('contacts/update', message);
|
|
||||||
} else if (message.event === 'set-custom-attributes') {
|
|
||||||
this.$store.dispatch(
|
|
||||||
'contacts/setCustomAttributes',
|
|
||||||
message.customAttributes
|
|
||||||
);
|
|
||||||
} else if (message.event === 'delete-custom-attribute') {
|
|
||||||
this.$store.dispatch('contacts/setCustomAttributes', {
|
|
||||||
[message.customAttribute]: null,
|
|
||||||
});
|
|
||||||
} else if (message.event === 'set-locale') {
|
|
||||||
this.setLocale(message.locale);
|
|
||||||
this.setBubbleLabel();
|
|
||||||
} else if (message.event === 'set-unread-view') {
|
|
||||||
this.showUnreadView = true;
|
|
||||||
} else if (message.event === 'unset-unread-view') {
|
|
||||||
this.showUnreadView = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$store.dispatch('conversationAttributes/get');
|
this.$store.dispatch('conversationAttributes/get');
|
||||||
|
this.setWidgetColor(window.chatwootWebChannel);
|
||||||
this.registerUnreadEvents();
|
this.registerUnreadEvents();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -147,15 +101,23 @@ export default {
|
||||||
this.hideMessageBubble = !!hideBubble;
|
this.hideMessageBubble = !!hideBubble;
|
||||||
},
|
},
|
||||||
registerUnreadEvents() {
|
registerUnreadEvents() {
|
||||||
bus.$on('on-agent-message-recieved', () => this.setUnreadView());
|
bus.$on('on-agent-message-recieved', () => {
|
||||||
|
if (!this.isIFrame) {
|
||||||
|
this.setUserLastSeen();
|
||||||
|
}
|
||||||
|
this.setUnreadView();
|
||||||
|
});
|
||||||
bus.$on('on-unread-view-clicked', () => {
|
bus.$on('on-unread-view-clicked', () => {
|
||||||
this.unsetUnreadView();
|
this.unsetUnreadView();
|
||||||
this.setUserLastSeen();
|
this.setUserLastSeen();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
setPopoutDisplay(showPopoutButton) {
|
||||||
|
this.showPopoutButton = showPopoutButton;
|
||||||
|
},
|
||||||
setUnreadView() {
|
setUnreadView() {
|
||||||
const { unreadMessageCount } = this;
|
const { unreadMessageCount } = this;
|
||||||
if (IFrameHelper.isIFrame() && unreadMessageCount > 0) {
|
if (this.isIFrame && unreadMessageCount > 0) {
|
||||||
IFrameHelper.sendMessage({
|
IFrameHelper.sendMessage({
|
||||||
event: 'setUnreadMode',
|
event: 'setUnreadMode',
|
||||||
unreadMessageCount,
|
unreadMessageCount,
|
||||||
|
@ -163,7 +125,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
unsetUnreadView() {
|
unsetUnreadView() {
|
||||||
if (IFrameHelper.isIFrame()) {
|
if (this.isIFrame) {
|
||||||
IFrameHelper.sendMessage({ event: 'resetUnreadMode' });
|
IFrameHelper.sendMessage({ event: 'resetUnreadMode' });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -176,6 +138,63 @@ export default {
|
||||||
this.setUserLastSeen();
|
this.setUserLastSeen();
|
||||||
this.$store.dispatch('events/create', { name: eventName });
|
this.$store.dispatch('events/create', { name: eventName });
|
||||||
},
|
},
|
||||||
|
registerListeners() {
|
||||||
|
const { websiteToken } = window.chatwootWebChannel;
|
||||||
|
window.addEventListener('message', e => {
|
||||||
|
if (!IFrameHelper.isAValidEvent(e)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const message = IFrameHelper.getMessage(e);
|
||||||
|
if (message.event === 'config-set') {
|
||||||
|
this.setLocale(message.locale);
|
||||||
|
this.setBubbleLabel();
|
||||||
|
this.setPosition(message.position);
|
||||||
|
this.fetchOldConversations().then(() => this.setUnreadView());
|
||||||
|
this.setPopoutDisplay(message.showPopoutButton);
|
||||||
|
this.fetchAvailableAgents(websiteToken);
|
||||||
|
this.setHideMessageBubble(message.hideMessageBubble);
|
||||||
|
} else if (message.event === 'widget-visible') {
|
||||||
|
this.scrollConversationToBottom();
|
||||||
|
} else if (message.event === 'set-current-url') {
|
||||||
|
window.refererURL = message.refererURL;
|
||||||
|
} else if (message.event === 'toggle-close-button') {
|
||||||
|
this.isMobile = message.showClose;
|
||||||
|
} else if (message.event === 'push-event') {
|
||||||
|
this.createWidgetEvents(message);
|
||||||
|
} else if (message.event === 'set-label') {
|
||||||
|
this.$store.dispatch('conversationLabels/create', message.label);
|
||||||
|
} else if (message.event === 'remove-label') {
|
||||||
|
this.$store.dispatch('conversationLabels/destroy', message.label);
|
||||||
|
} else if (message.event === 'set-user') {
|
||||||
|
this.$store.dispatch('contacts/update', message);
|
||||||
|
} else if (message.event === 'set-custom-attributes') {
|
||||||
|
this.$store.dispatch(
|
||||||
|
'contacts/setCustomAttributes',
|
||||||
|
message.customAttributes
|
||||||
|
);
|
||||||
|
} else if (message.event === 'delete-custom-attribute') {
|
||||||
|
this.$store.dispatch('contacts/setCustomAttributes', {
|
||||||
|
[message.customAttribute]: null,
|
||||||
|
});
|
||||||
|
} else if (message.event === 'set-locale') {
|
||||||
|
this.setLocale(message.locale);
|
||||||
|
this.setBubbleLabel();
|
||||||
|
} else if (message.event === 'set-unread-view') {
|
||||||
|
this.showUnreadView = true;
|
||||||
|
} else if (message.event === 'unset-unread-view') {
|
||||||
|
this.showUnreadView = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
sendLoadedEvent() {
|
||||||
|
IFrameHelper.sendMessage({
|
||||||
|
event: 'loaded',
|
||||||
|
config: {
|
||||||
|
authToken: window.authToken,
|
||||||
|
channelConfig: window.chatwootWebChannel,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -58,4 +58,14 @@ $button-border-width: 1px;
|
||||||
&.block {
|
&.block {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.transparent {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.compact {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@ html,
|
||||||
body {
|
body {
|
||||||
font-family: $font-family;
|
font-family: $font-family;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,36 +20,15 @@ body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-button {
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
width: $space-two;
|
|
||||||
|
|
||||||
&::before,
|
|
||||||
&::after {
|
|
||||||
background-color: $color-heading;
|
|
||||||
content: ' ';
|
|
||||||
height: $space-normal;
|
|
||||||
left: $space-small;
|
|
||||||
position: absolute;
|
|
||||||
top: $space-micro;
|
|
||||||
width: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
transform: rotate(45deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
transform: rotate(-45deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-mobile {
|
.is-mobile {
|
||||||
.header-wrap {
|
.actions {
|
||||||
.close-button {
|
.close-button {
|
||||||
display: block !important;
|
display: block !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.new-window--button {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,16 +4,18 @@
|
||||||
<img v-if="avatarUrl" :src="avatarUrl" alt="avatar" />
|
<img v-if="avatarUrl" :src="avatarUrl" alt="avatar" />
|
||||||
<h2 class="title" v-html="title"></h2>
|
<h2 class="title" v-html="title"></h2>
|
||||||
</div>
|
</div>
|
||||||
<span class="close-button" @click="closeWindow"></span>
|
<header-actions :show-popout-button="showPopoutButton" />
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { IFrameHelper } from 'widget/helpers/utils';
|
import HeaderActions from './HeaderActions';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ChatHeader',
|
name: 'ChatHeader',
|
||||||
|
components: {
|
||||||
|
HeaderActions,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
avatarUrl: {
|
avatarUrl: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -23,21 +25,16 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
showPopoutButton: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
widgetColor: 'appConfig/getWidgetColor',
|
widgetColor: 'appConfig/getWidgetColor',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
closeWindow() {
|
|
||||||
if (IFrameHelper.isIFrame()) {
|
|
||||||
IFrameHelper.sendMessage({
|
|
||||||
event: 'toggleBubble',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -73,9 +70,5 @@ export default {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
margin-right: $space-small;
|
margin-right: $space-small;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-button {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<header class="header-expanded">
|
<header class="header-expanded">
|
||||||
<img v-if="avatarUrl" class="logo" :src="avatarUrl" />
|
<div class="header--row">
|
||||||
<span class="close close-button" @click="closeWindow"></span>
|
<img v-if="avatarUrl" class="logo" :src="avatarUrl" />
|
||||||
|
<header-actions :show-popout-button="showPopoutButton" />
|
||||||
|
</div>
|
||||||
<h2 class="title" v-html="introHeading"></h2>
|
<h2 class="title" v-html="introHeading"></h2>
|
||||||
<p class="body" v-html="introBody"></p>
|
<p class="body" v-html="introBody"></p>
|
||||||
</header>
|
</header>
|
||||||
|
@ -9,10 +11,12 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { IFrameHelper } from 'widget/helpers/utils';
|
import HeaderActions from './HeaderActions';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ChatHeaderExpanded',
|
name: 'ChatHeaderExpanded',
|
||||||
|
components: {
|
||||||
|
HeaderActions,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
avatarUrl: {
|
avatarUrl: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -26,21 +30,16 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
showPopoutButton: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
widgetColor: 'appConfig/getWidgetColor',
|
widgetColor: 'appConfig/getWidgetColor',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
closeWindow() {
|
|
||||||
if (IFrameHelper.isIFrame()) {
|
|
||||||
IFrameHelper.sendMessage({
|
|
||||||
event: 'toggleBubble',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -59,12 +58,6 @@ export default {
|
||||||
height: 56px;
|
height: 56px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close {
|
|
||||||
position: absolute;
|
|
||||||
right: $space-medium;
|
|
||||||
top: $space-medium;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.title {
|
.title {
|
||||||
color: $color-heading;
|
color: $color-heading;
|
||||||
font-size: $font-size-mega;
|
font-size: $font-size-mega;
|
||||||
|
@ -79,4 +72,10 @@ export default {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header--row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
89
app/javascript/widget/components/HeaderActions.vue
Normal file
89
app/javascript/widget/components/HeaderActions.vue
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="isIframe" class="actions">
|
||||||
|
<button
|
||||||
|
v-if="showPopoutButton"
|
||||||
|
class="button transparent compact new-window--button"
|
||||||
|
@click="popoutWindow"
|
||||||
|
>
|
||||||
|
<span class="ion-android-open"></span>
|
||||||
|
</button>
|
||||||
|
<button class="button transparent compact close-button">
|
||||||
|
<span class="ion-android-close" @click="closeWindow"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { IFrameHelper } from 'widget/helpers/utils';
|
||||||
|
import { buildPopoutURL } from '../helpers/urlParamsHelper';
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'HeaderActions',
|
||||||
|
props: {
|
||||||
|
showPopoutButton: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isIframe() {
|
||||||
|
return IFrameHelper.isIFrame();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
popoutWindow() {
|
||||||
|
this.closeWindow();
|
||||||
|
const {
|
||||||
|
location: { origin },
|
||||||
|
chatwootWebChannel: { websiteToken },
|
||||||
|
authToken,
|
||||||
|
} = window;
|
||||||
|
|
||||||
|
const popoutWindowURL = buildPopoutURL({
|
||||||
|
origin,
|
||||||
|
websiteToken,
|
||||||
|
locale: Vue.config.lang,
|
||||||
|
conversationCookie: authToken,
|
||||||
|
});
|
||||||
|
const popoutWindow = window.open(
|
||||||
|
popoutWindowURL,
|
||||||
|
`webwidget_session_${websiteToken}`,
|
||||||
|
'resizable=off,width=400,height=600'
|
||||||
|
);
|
||||||
|
popoutWindow.focus();
|
||||||
|
},
|
||||||
|
closeWindow() {
|
||||||
|
if (IFrameHelper.isIFrame()) {
|
||||||
|
IFrameHelper.sendMessage({
|
||||||
|
event: 'toggleBubble',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '~widget/assets/scss/variables.scss';
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-left: $space-normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: $color-heading;
|
||||||
|
font-size: $font-size-large;
|
||||||
|
|
||||||
|
&.ion-android-close {
|
||||||
|
font-size: $font-size-big;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -12,3 +12,5 @@ export const MESSAGE_TYPE = {
|
||||||
ACTIVITY: 2,
|
ACTIVITY: 2,
|
||||||
TEMPLATE: 3,
|
TEMPLATE: 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const WOOT_PREFIX = 'chatwoot-widget:';
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
import { buildSearchParamsWithLocale } from '../urlParamsHelper';
|
import {
|
||||||
|
buildSearchParamsWithLocale,
|
||||||
|
getLocale,
|
||||||
|
buildPopoutURL,
|
||||||
|
} from '../urlParamsHelper';
|
||||||
|
|
||||||
jest.mock('vue', () => ({
|
jest.mock('vue', () => ({
|
||||||
config: {
|
config: {
|
||||||
|
@ -14,3 +18,29 @@ describe('#buildSearchParamsWithLocale', () => {
|
||||||
expect(buildSearchParamsWithLocale('')).toEqual('?locale=el');
|
expect(buildSearchParamsWithLocale('')).toEqual('?locale=el');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#getLocale', () => {
|
||||||
|
it('returns correct locale', () => {
|
||||||
|
expect(getLocale('?test=1&cw_conv=2&locale=fr')).toEqual('fr');
|
||||||
|
expect(getLocale('?test=1&locale=fr')).toEqual('fr');
|
||||||
|
expect(getLocale('?test=1&cw_conv=2&website_token=3&locale=fr')).toEqual(
|
||||||
|
'fr'
|
||||||
|
);
|
||||||
|
expect(getLocale('')).toEqual(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#buildPopoutURL', () => {
|
||||||
|
it('returns popout URL', () => {
|
||||||
|
expect(
|
||||||
|
buildPopoutURL({
|
||||||
|
origin: 'https://chatwoot.com',
|
||||||
|
conversationCookie: 'random-jwt-token',
|
||||||
|
websiteToken: 'random-website-token',
|
||||||
|
locale: 'ar',
|
||||||
|
})
|
||||||
|
).toEqual(
|
||||||
|
'https://chatwoot.com/widget?cw_conversation=random-jwt-token&website_token=random-website-token&locale=ar'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
42
app/javascript/widget/helpers/specs/utils.spec.js
Normal file
42
app/javascript/widget/helpers/specs/utils.spec.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import { IFrameHelper } from '../utils';
|
||||||
|
|
||||||
|
jest.mock('vue', () => ({
|
||||||
|
config: {
|
||||||
|
lang: 'el',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('#IFrameHelper', () => {
|
||||||
|
describe('#isAValidEvent', () => {
|
||||||
|
it('returns if the event is valid', () => {
|
||||||
|
expect(
|
||||||
|
IFrameHelper.isAValidEvent({
|
||||||
|
data:
|
||||||
|
'chatwoot-widget:{"event":"config-set","locale":"fr","position":"left","hideMessageBubble":false,"showPopoutButton":true}',
|
||||||
|
})
|
||||||
|
).toEqual(true);
|
||||||
|
expect(
|
||||||
|
IFrameHelper.isAValidEvent({
|
||||||
|
data:
|
||||||
|
'{"event":"config-set","locale":"fr","position":"left","hideMessageBubble":false,"showPopoutButton":true}',
|
||||||
|
})
|
||||||
|
).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#getMessage', () => {
|
||||||
|
it('returns parsed message', () => {
|
||||||
|
expect(
|
||||||
|
IFrameHelper.getMessage({
|
||||||
|
data:
|
||||||
|
'chatwoot-widget:{"event":"config-set","locale":"fr","position":"left","hideMessageBubble":false,"showPopoutButton":true}',
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
event: 'config-set',
|
||||||
|
locale: 'fr',
|
||||||
|
position: 'left',
|
||||||
|
hideMessageBubble: false,
|
||||||
|
showPopoutButton: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,4 +1,5 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
export const buildSearchParamsWithLocale = search => {
|
export const buildSearchParamsWithLocale = search => {
|
||||||
const locale = Vue.config.lang;
|
const locale = Vue.config.lang;
|
||||||
if (search) {
|
if (search) {
|
||||||
|
@ -8,3 +9,23 @@ export const buildSearchParamsWithLocale = search => {
|
||||||
}
|
}
|
||||||
return search;
|
return search;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getLocale = (search = '') => {
|
||||||
|
const searchParamKeyValuePairs = search.split('&');
|
||||||
|
return searchParamKeyValuePairs.reduce((acc, keyValuePair) => {
|
||||||
|
const [key, value] = keyValuePair.split('=');
|
||||||
|
if (key === 'locale') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildPopoutURL = ({
|
||||||
|
origin,
|
||||||
|
conversationCookie,
|
||||||
|
websiteToken,
|
||||||
|
locale,
|
||||||
|
}) => {
|
||||||
|
return `${origin}/widget?cw_conversation=${conversationCookie}&website_token=${websiteToken}&locale=${locale}`;
|
||||||
|
};
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { WOOT_PREFIX } from './constants';
|
||||||
|
|
||||||
export const isEmptyObject = obj =>
|
export const isEmptyObject = obj =>
|
||||||
Object.keys(obj).length === 0 && obj.constructor === Object;
|
Object.keys(obj).length === 0 && obj.constructor === Object;
|
||||||
|
|
||||||
|
@ -16,4 +18,11 @@ export const IFrameHelper = {
|
||||||
'*'
|
'*'
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
isAValidEvent: e => {
|
||||||
|
const isDataAString = typeof e.data === 'string';
|
||||||
|
const isAValidWootEvent =
|
||||||
|
isDataAString && e.data.indexOf(WOOT_PREFIX) === 0;
|
||||||
|
return isAValidWootEvent;
|
||||||
|
},
|
||||||
|
getMessage: e => JSON.parse(e.data.replace(WOOT_PREFIX, '')),
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,11 +6,13 @@
|
||||||
:intro-heading="introHeading"
|
:intro-heading="introHeading"
|
||||||
:intro-body="introBody"
|
:intro-body="introBody"
|
||||||
:avatar-url="channelConfig.avatarUrl"
|
:avatar-url="channelConfig.avatarUrl"
|
||||||
|
:show-popout-button="showPopoutButton"
|
||||||
/>
|
/>
|
||||||
<ChatHeader
|
<ChatHeader
|
||||||
v-else
|
v-else
|
||||||
:title="channelConfig.websiteName"
|
:title="channelConfig.websiteName"
|
||||||
:avatar-url="channelConfig.avatarUrl"
|
:avatar-url="channelConfig.avatarUrl"
|
||||||
|
:show-popout-button="showPopoutButton"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AvailableAgents v-if="showAvailableAgents" :agents="availableAgents" />
|
<AvailableAgents v-if="showAvailableAgents" :agents="availableAgents" />
|
||||||
|
@ -69,6 +71,10 @@ export default {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
showPopoutButton: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isOpen() {
|
isOpen() {
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
:has-fetched="hasFetched"
|
:has-fetched="hasFetched"
|
||||||
:conversation-attributes="conversationAttributes"
|
:conversation-attributes="conversationAttributes"
|
||||||
:unread-message-count="unreadMessageCount"
|
:unread-message-count="unreadMessageCount"
|
||||||
|
:show-popout-button="showPopoutButton"
|
||||||
/>
|
/>
|
||||||
<unread
|
<unread
|
||||||
v-else
|
v-else
|
||||||
|
@ -81,6 +82,10 @@ export default {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
showPopoutButton: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -5,8 +5,9 @@
|
||||||
window.chatwootSettings = {
|
window.chatwootSettings = {
|
||||||
hideMessageBubble: false,
|
hideMessageBubble: false,
|
||||||
position: 'left',
|
position: 'left',
|
||||||
locale: 'en',
|
locale: 'fr',
|
||||||
type: 'expanded_bubble',
|
type: 'expanded_bubble',
|
||||||
|
showPopoutButton: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
(function(d,t) {
|
(function(d,t) {
|
||||||
|
|
|
@ -46,6 +46,17 @@ window.chatwootSettings = {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### To enable popout window
|
||||||
|
|
||||||
|
Inorder to enable the popout window, add the following configuration to `chatwootSettings`. This option is disabled by default.
|
||||||
|
|
||||||
|
```js
|
||||||
|
window.chatwootSettings = {
|
||||||
|
// ...Other Config
|
||||||
|
showPopoutButton: true,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### To trigger widget without displaying bubble
|
### To trigger widget without displaying bubble
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue