chore: Add an indicator for incoming emails (#1112)
This commit is contained in:
@@ -8,21 +8,9 @@
|
||||
font-weight: $font-weight-normal;
|
||||
position: relative;
|
||||
|
||||
.icon {
|
||||
bottom: $space-smaller;
|
||||
position: absolute;
|
||||
right: $space-small;
|
||||
}
|
||||
|
||||
.message-text__wrap {
|
||||
position: relative;
|
||||
|
||||
.time {
|
||||
color: $color-primary-light;
|
||||
display: block;
|
||||
font-size: $font-size-micro;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: $color-white;
|
||||
@@ -37,24 +25,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.audio {
|
||||
.time {
|
||||
margin-top: -$space-two;
|
||||
}
|
||||
}
|
||||
|
||||
.image {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
.time {
|
||||
bottom: $space-smaller;
|
||||
color: $color-white;
|
||||
position: absolute;
|
||||
right: $space-small;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -74,30 +48,6 @@
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.map {
|
||||
@include flex;
|
||||
flex-direction: column;
|
||||
text-align: right;
|
||||
|
||||
img {
|
||||
@include padding($space-small);
|
||||
max-height: 30rem;
|
||||
max-width: 20rem;
|
||||
}
|
||||
|
||||
.time {
|
||||
@include padding($space-small);
|
||||
margin-left: -$space-smaller;
|
||||
margin-top: -$space-two;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.locname {
|
||||
font-weight: $font-weight-medium;
|
||||
padding: $space-smaller;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.conversations-sidebar {
|
||||
@@ -257,14 +207,6 @@
|
||||
color: $color-body;
|
||||
margin-right: auto;
|
||||
|
||||
.time {
|
||||
color: $color-light-gray;
|
||||
}
|
||||
|
||||
.image .time {
|
||||
color: $color-white;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: $color-primary-dark;
|
||||
}
|
||||
@@ -321,10 +263,6 @@
|
||||
right: $space-one;
|
||||
top: $space-smaller + $space-micro;
|
||||
}
|
||||
|
||||
.time {
|
||||
color: $color-light-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,11 +327,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.time {
|
||||
color: $medium-gray;
|
||||
font-size: $font-size-micro;
|
||||
margin-left: $space-slab;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<bubble-text
|
||||
v-if="data.content"
|
||||
:message="message"
|
||||
:is-email="isEmailContentType"
|
||||
:readable-time="readableTime"
|
||||
/>
|
||||
<span v-if="hasAttachments">
|
||||
@@ -21,12 +22,10 @@
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<i
|
||||
v-if="isPrivate"
|
||||
v-tooltip.top-start="toolTipMessage"
|
||||
class="icon ion-android-lock"
|
||||
@mouseenter="isHovered = true"
|
||||
@mouseleave="isHovered = false"
|
||||
<bubble-actions
|
||||
:is-email="isEmailContentType"
|
||||
:readable-time="readableTime"
|
||||
:is-private="data.private"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
@@ -39,14 +38,16 @@ import timeMixin from '../../../mixins/time';
|
||||
import BubbleText from './bubble/Text';
|
||||
import BubbleImage from './bubble/Image';
|
||||
import BubbleFile from './bubble/File';
|
||||
|
||||
import contentTypeMixin from 'shared/mixins/contentTypeMixin';
|
||||
import BubbleActions from './bubble/Actions';
|
||||
export default {
|
||||
components: {
|
||||
BubbleActions,
|
||||
BubbleText,
|
||||
BubbleImage,
|
||||
BubbleFile,
|
||||
},
|
||||
mixins: [timeMixin, messageFormatterMixin],
|
||||
mixins: [timeMixin, messageFormatterMixin, contentTypeMixin],
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
@@ -62,6 +63,12 @@ export default {
|
||||
message() {
|
||||
return this.formatMessage(this.data.content);
|
||||
},
|
||||
contentType() {
|
||||
const {
|
||||
data: { content_type: contentType },
|
||||
} = this;
|
||||
return contentType;
|
||||
},
|
||||
alignBubble() {
|
||||
return !this.data.message_type ? 'left' : 'right';
|
||||
},
|
||||
@@ -82,14 +89,6 @@ export default {
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isPrivate() {
|
||||
return this.data.private;
|
||||
},
|
||||
toolTipMessage() {
|
||||
return this.data.private
|
||||
? { content: this.$t('CONVERSATION.VISIBLE_TO_AGENTS'), classes: 'top' }
|
||||
: false;
|
||||
},
|
||||
sentByMessage() {
|
||||
const { sender } = this.data;
|
||||
|
||||
@@ -109,7 +108,7 @@ export default {
|
||||
bubbleClass() {
|
||||
return {
|
||||
bubble: this.isBubble,
|
||||
'is-private': this.isPrivate,
|
||||
'is-private': this.data.private,
|
||||
'is-image': this.hasImageAttachment,
|
||||
};
|
||||
},
|
||||
@@ -119,13 +118,10 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import '~dashboard/assets/scss/variables.scss';
|
||||
.wrap {
|
||||
.is-image {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
<style lang="scss">
|
||||
.wrap > .is-image.bubble {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
|
||||
.image {
|
||||
max-width: 32rem;
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<div class="message-text--metadata">
|
||||
<span class="time">{{ readableTime }}</span>
|
||||
<i
|
||||
v-if="isEmail"
|
||||
v-tooltip.top-start="$t('CHAT_LIST.RECEIVED_VIA_EMAIL')"
|
||||
class="ion ion-android-mail"
|
||||
/>
|
||||
<i
|
||||
v-if="isPrivate"
|
||||
v-tooltip.top-start="$t('CONVERSATION.VISIBLE_TO_AGENTS')"
|
||||
class="icon ion-android-lock"
|
||||
@mouseenter="isHovered = true"
|
||||
@mouseleave="isHovered = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
readableTime: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isEmail: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
isPrivate: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.right {
|
||||
.message-text--metadata {
|
||||
.time {
|
||||
color: var(--w-100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.left {
|
||||
.message-text--metadata {
|
||||
.time {
|
||||
color: var(--s-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message-text--metadata {
|
||||
align-items: flex-end;
|
||||
display: flex;
|
||||
|
||||
.time {
|
||||
margin-right: var(--space-small);
|
||||
display: block;
|
||||
font-size: var(--font-size-micro);
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
i {
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.activity-wrap {
|
||||
.message-text--metadata {
|
||||
display: inline-block;
|
||||
|
||||
.time {
|
||||
color: var(--s-300);
|
||||
font-size: var(--font-size-micro);
|
||||
margin-left: var(--space-small);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-image {
|
||||
.message-text--metadata {
|
||||
.time {
|
||||
bottom: var(--space-smaller);
|
||||
color: var(--white);
|
||||
position: absolute;
|
||||
right: var(--space-small);
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-private {
|
||||
.message-text--metadata {
|
||||
align-items: flex-end;
|
||||
|
||||
.time {
|
||||
color: var(--s-400);
|
||||
}
|
||||
}
|
||||
|
||||
&.is-image {
|
||||
.time {
|
||||
position: inherit;
|
||||
padding-left: var(--space-one);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,41 +0,0 @@
|
||||
<template>
|
||||
<div class="audio message-text__wrap">
|
||||
<a-player
|
||||
:music="playerOptions"
|
||||
mode="order"
|
||||
/>
|
||||
<span class="time">{{readableTime}}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import APlayer from 'vue-aplayer';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
APlayer,
|
||||
},
|
||||
props: [
|
||||
'url',
|
||||
'readableTime',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
musicObj: {
|
||||
title: ' ',
|
||||
author: ' ',
|
||||
autoplay: false,
|
||||
narrow: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
playerOptions() {
|
||||
return {
|
||||
...this.musicObj,
|
||||
url: this.url,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -16,13 +16,17 @@
|
||||
{{ $t('CONVERSATION.DOWNLOAD') }}
|
||||
</a>
|
||||
</div>
|
||||
<span class="time">{{ readableTime }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['url', 'readableTime'],
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
fileName() {
|
||||
const filename = this.url.substring(this.url.lastIndexOf('/') + 1);
|
||||
@@ -31,7 +35,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
openLink() {
|
||||
const win = window.open(this.url, '_blank');
|
||||
const win = window.open(this.url, '_blank', 'noopener');
|
||||
win.focus();
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,25 +1,20 @@
|
||||
<template>
|
||||
<div class="image message-text__wrap">
|
||||
<img
|
||||
:src="url"
|
||||
v-on:click="onClick"
|
||||
/>
|
||||
<span class="time">{{readableTime}}</span>
|
||||
<img :src="url" @click="onClick" />
|
||||
<woot-modal :show.sync="show" :on-close="onClose">
|
||||
<img
|
||||
:src="url"
|
||||
class="modal-image"
|
||||
/>
|
||||
<img :src="url" class="modal-image" />
|
||||
</woot-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'url',
|
||||
'readableTime',
|
||||
],
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<template>
|
||||
<div class="map message-text__wrap">
|
||||
<img
|
||||
:src="locUrl"
|
||||
/>
|
||||
<span class="locname">{{label || ' '}}</span>
|
||||
<span class="time">{{readableTime}}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'lat',
|
||||
'lng',
|
||||
'label',
|
||||
'readableTime',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
accessToken: 'pk.eyJ1IjoiY2hhdHdvb3QiLCJhIjoiY2oyazVsM3d0MDBmYjJxbmkyYXlwY3hzZyJ9.uWUdfItb0sSZQ4nfwlmuPg',
|
||||
zoomLevel: 14,
|
||||
mapType: 'mapbox.streets',
|
||||
apiEndPoint: 'https://api.mapbox.com/v4/',
|
||||
h: 100,
|
||||
w: 150,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
locUrl() {
|
||||
const { apiEndPoint, mapType, lat, lng, zoomLevel, h, w, accessToken } = this;
|
||||
return `${apiEndPoint}${mapType}/${lng},${lat},${zoomLevel}/${w}x${h}.png?access_token=${accessToken}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,12 +1,24 @@
|
||||
<template>
|
||||
<span class="message-text__wrap">
|
||||
<span v-html="message"></span>
|
||||
<span class="time">{{ readableTime }}</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['message', 'readableTime'],
|
||||
props: {
|
||||
message: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
readableTime: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isEmail: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
"ICON": "ion-link",
|
||||
"CONTENT": "has shared a url"
|
||||
}
|
||||
}
|
||||
},
|
||||
"RECEIVED_VIA_EMAIL": "Received via email"
|
||||
}
|
||||
}
|
||||
|
||||
3
app/javascript/shared/constants/contentType.js
Normal file
3
app/javascript/shared/constants/contentType.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export const CONTENT_TYPES = {
|
||||
INCOMING_EMAIL: 'incoming_email',
|
||||
};
|
||||
9
app/javascript/shared/mixins/contentTypeMixin.js
Normal file
9
app/javascript/shared/mixins/contentTypeMixin.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { CONTENT_TYPES } from '../constants/contentType';
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
isEmailContentType() {
|
||||
return this.contentType === CONTENT_TYPES.INCOMING_EMAIL;
|
||||
},
|
||||
},
|
||||
};
|
||||
32
app/javascript/shared/mixins/specs/contentTypeMixin.spec.js
Normal file
32
app/javascript/shared/mixins/specs/contentTypeMixin.spec.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import contentTypeMixin from '../contentTypeMixin';
|
||||
|
||||
describe('contentTypeMixin', () => {
|
||||
it('returns true if contentType is incoming_email', () => {
|
||||
const Component = {
|
||||
render() {},
|
||||
mixins: [contentTypeMixin],
|
||||
computed: {
|
||||
contentType() {
|
||||
return 'incoming_email';
|
||||
},
|
||||
},
|
||||
};
|
||||
const wrapper = shallowMount(Component);
|
||||
expect(wrapper.vm.isEmailContentType).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if contentType is not incoming_email', () => {
|
||||
const Component = {
|
||||
render() {},
|
||||
mixins: [contentTypeMixin],
|
||||
computed: {
|
||||
contentType() {
|
||||
return 'input_select';
|
||||
},
|
||||
},
|
||||
};
|
||||
const wrapper = shallowMount(Component);
|
||||
expect(wrapper.vm.isEmailContentType).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user