feat: Redesigned search UI (#1845)
This commit is contained in:
parent
c99c63cd79
commit
36f086c5cb
7 changed files with 591 additions and 215 deletions
|
@ -11,7 +11,7 @@
|
|||
"TITLE": "Search messages",
|
||||
"LOADING_MESSAGE": "Crunching data...",
|
||||
"PLACEHOLDER": "Type any text to search messages",
|
||||
"NO_MATCHING_RESULTS": "There are no messages matching the search parameters."
|
||||
"NO_MATCHING_RESULTS": "No results found."
|
||||
},
|
||||
"UNREAD_MESSAGES": "Unread Messages",
|
||||
"UNREAD_MESSAGE": "Unread Message",
|
||||
|
|
|
@ -6,17 +6,7 @@
|
|||
:active-team="activeTeam"
|
||||
@conversation-load="onConversationLoad"
|
||||
>
|
||||
<button class="search--button" @click="onSearch">
|
||||
<i class="ion-ios-search-strong search--icon" />
|
||||
<div class="text-truncate">
|
||||
{{ $t('CONVERSATION.SEARCH_MESSAGES') }}
|
||||
</div>
|
||||
</button>
|
||||
<search
|
||||
v-if="showSearchModal"
|
||||
:show="showSearchModal"
|
||||
:on-close="closeSearch"
|
||||
/>
|
||||
<pop-over-search />
|
||||
</chat-list>
|
||||
<conversation-box
|
||||
:inbox-id="inboxId"
|
||||
|
@ -29,17 +19,16 @@
|
|||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
import ChatList from '../../../components/ChatList';
|
||||
import ConversationBox from '../../../components/widgets/conversation/ConversationBox';
|
||||
import Search from './search/Search.vue';
|
||||
import PopOverSearch from './search/PopOverSearch';
|
||||
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ChatList,
|
||||
ConversationBox,
|
||||
Search,
|
||||
PopOverSearch,
|
||||
},
|
||||
mixins: [uiSettingsMixin],
|
||||
props: {
|
||||
|
@ -146,31 +135,6 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.search--button {
|
||||
align-items: center;
|
||||
border: 0;
|
||||
color: var(--s-400);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: 400;
|
||||
padding: var(--space-normal) var(--space-normal) var(--space-slab);
|
||||
text-align: left;
|
||||
line-height: var(--font-size-large);
|
||||
|
||||
&:hover {
|
||||
.search--icon {
|
||||
color: var(--w-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search--icon {
|
||||
color: var(--s-600);
|
||||
font-size: var(--font-size-large);
|
||||
padding-right: var(--space-small);
|
||||
}
|
||||
|
||||
.conversation-page {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
|
|
@ -0,0 +1,228 @@
|
|||
<template>
|
||||
<div v-on-clickaway="closeSearch" class="search-wrap">
|
||||
<div class="search" :class="{ 'is-active': showSearchBox }">
|
||||
<div class="icon">
|
||||
<i class="ion-ios-search-strong search--icon" />
|
||||
</div>
|
||||
<input
|
||||
v-model="searchTerm"
|
||||
class="search--input"
|
||||
:placeholder="$t('CONVERSATION.SEARCH_MESSAGES')"
|
||||
@focus="onSearch"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="showSearchBox" class="results-wrap">
|
||||
<div class="show-results">
|
||||
<div>
|
||||
<div class="result-view">
|
||||
<div class="result">
|
||||
Search Results
|
||||
<span v-if="resultsCount" class="message-counter">
|
||||
({{ resultsCount }})
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="uiFlags.isFetching" class="search--activity-message">
|
||||
<woot-spinner size="" />
|
||||
{{ $t('CONVERSATION.SEARCH.LOADING_MESSAGE') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showSearchResult" class="search-results--container">
|
||||
<result-item
|
||||
v-for="conversation in conversations"
|
||||
:key="conversation.messageId"
|
||||
:conversation-id="conversation.id"
|
||||
:user-name="conversation.contact.name"
|
||||
:timestamp="conversation.created_at"
|
||||
:messages="conversation.messages"
|
||||
:search-term="searchTerm"
|
||||
:inbox-name="conversation.inbox.name"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="showEmptyResult" class="search--activity-no-message">
|
||||
{{ $t('CONVERSATION.SEARCH.NO_MATCHING_RESULTS') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import { mapGetters } from 'vuex';
|
||||
import timeMixin from '../../../../mixins/time';
|
||||
import ResultItem from './ResultItem';
|
||||
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ResultItem,
|
||||
},
|
||||
|
||||
directives: {
|
||||
focus: {
|
||||
inserted(el) {
|
||||
el.focus();
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
mixins: [timeMixin, messageFormatterMixin, clickaway],
|
||||
|
||||
data() {
|
||||
return {
|
||||
searchTerm: '',
|
||||
showSearchBox: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
conversations: 'conversationSearch/getConversations',
|
||||
uiFlags: 'conversationSearch/getUIFlags',
|
||||
}),
|
||||
resultsCount() {
|
||||
return this.conversations.length;
|
||||
},
|
||||
showSearchResult() {
|
||||
return (
|
||||
this.searchTerm && this.conversations.length && !this.uiFlags.isFetching
|
||||
);
|
||||
},
|
||||
showEmptyResult() {
|
||||
return (
|
||||
this.searchTerm &&
|
||||
!this.conversations.length &&
|
||||
!this.uiFlags.isFetching
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
searchTerm(newValue) {
|
||||
if (this.typingTimer) {
|
||||
clearTimeout(this.typingTimer);
|
||||
}
|
||||
|
||||
this.typingTimer = setTimeout(() => {
|
||||
this.hasSearched = true;
|
||||
this.$store.dispatch('conversationSearch/get', { q: newValue });
|
||||
}, 1000);
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$store.dispatch('conversationSearch/get', { q: '' });
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSearch() {
|
||||
this.showSearchBox = true;
|
||||
},
|
||||
closeSearch() {
|
||||
this.showSearchBox = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search {
|
||||
display: flex;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid transparent;
|
||||
padding: var(--space-one) var(--space-normal) var(--space-smaller)
|
||||
var(--space-normal);
|
||||
|
||||
&:hover {
|
||||
.search--icon {
|
||||
color: var(--w-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search--input {
|
||||
align-items: center;
|
||||
border: 0;
|
||||
color: var(--color-body);
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-normal);
|
||||
text-align: left;
|
||||
line-height: var(--font-size-large);
|
||||
}
|
||||
|
||||
.search--icon {
|
||||
color: var(--s-600);
|
||||
font-size: var(--font-size-large);
|
||||
padding: 0 var(--space-small) 0 0;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
color: var(--color-body);
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
||||
.results-wrap {
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
box-shadow: var(--shadow-large);
|
||||
background: white;
|
||||
width: 100%;
|
||||
max-height: 70vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.show-results {
|
||||
list-style-type: none;
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
|
||||
.result-view {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.result {
|
||||
padding: var(--space-smaller) var(--space-smaller) var(--space-smaller)
|
||||
var(--space-normal);
|
||||
color: var(--s-700);
|
||||
font-size: var(--font-size-medium);
|
||||
font-weight: var(--font-weight-bold);
|
||||
|
||||
.message-counter {
|
||||
color: var(--s-500);
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
}
|
||||
|
||||
.search--activity-message {
|
||||
padding: var(--space-small) var(--space-normal) var(--space-small)
|
||||
var(--space-zero);
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--s-500);
|
||||
}
|
||||
|
||||
.search--activity-no-message {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: var(--space-one) var(--space-zero) var(--space-two) var(--space-zero);
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--s-500);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,191 @@
|
|||
<template>
|
||||
<div class="search-result" @click="onClick">
|
||||
<div class="result-header">
|
||||
<div class="message">
|
||||
<i class="ion-ios-chatboxes-outline" />
|
||||
<div class="conversation">
|
||||
<div class="name-wrap">
|
||||
<span class="user-name">{{ userName }}</span>
|
||||
<div>
|
||||
<span class="conversation-id"># {{ conversationId }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="inbox-name">{{ inboxName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="timestamp">{{ readableTime }} </span>
|
||||
</div>
|
||||
<search-message-item
|
||||
v-for="message in messages"
|
||||
:key="message.created_at"
|
||||
:user-name="message.sender_name"
|
||||
:timestamp="message.created_at"
|
||||
:message-type="message.message_type"
|
||||
:content="message.content"
|
||||
:search-term="searchTerm"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { frontendURL, conversationUrl } from 'dashboard/helper/URLHelper';
|
||||
import timeMixin from 'dashboard/mixins/time';
|
||||
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||
import SearchMessageItem from './SearchMessageItem.vue';
|
||||
|
||||
export default {
|
||||
components: { SearchMessageItem },
|
||||
mixins: [timeMixin, messageFormatterMixin],
|
||||
|
||||
props: {
|
||||
conversationId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
userName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
inboxName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
timestamp: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
messages: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
searchTerm: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
accountId: 'getCurrentAccountId',
|
||||
}),
|
||||
readableTime() {
|
||||
if (!this.timestamp) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return this.dynamicTime(this.timestamp);
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onClick() {
|
||||
const path = conversationUrl({
|
||||
accountId: this.accountId,
|
||||
id: this.conversationId,
|
||||
});
|
||||
window.location = frontendURL(path);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search-result {
|
||||
display: block;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
color: var(--color-body);
|
||||
padding: var(--space-smaller) var(--space-two) 0 var(--space-normal);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: var(--space-normal);
|
||||
}
|
||||
}
|
||||
|
||||
.result-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background: var(--color-background);
|
||||
padding: var(--space-smaller) var(--space-slab);
|
||||
margin-bottom: var(--space-small);
|
||||
border-radius: var(--border-radius-medium);
|
||||
|
||||
&:hover {
|
||||
background: var(--w-400);
|
||||
color: var(--white);
|
||||
.inbox-name {
|
||||
color: var(--white);
|
||||
}
|
||||
.timestamp {
|
||||
color: var(--white);
|
||||
}
|
||||
.ion-ios-chatboxes-outline {
|
||||
color: var(--white);
|
||||
}
|
||||
.conversation-id {
|
||||
background: var(--w-50);
|
||||
color: var(--s-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ion-ios-chatboxes-outline {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-size: var(--font-size-large);
|
||||
color: var(--w-500);
|
||||
}
|
||||
|
||||
.conversation {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(--space-smaller) var(--space-one);
|
||||
}
|
||||
|
||||
.name-wrap {
|
||||
display: flex;
|
||||
width: 20rem;
|
||||
|
||||
.user-name {
|
||||
font-size: var(--font-size-default);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-right: var(--space-micro);
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.conversation-id {
|
||||
background: var(--w-400);
|
||||
border-radius: var(--border-radius-normal);
|
||||
color: var(--w-50);
|
||||
align-items: center;
|
||||
font-size: var(--font-size-mini);
|
||||
font-weight: var(--font-weight-bold);
|
||||
padding: 0 var(--space-smaller);
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.inbox-name {
|
||||
border-radius: var(--border-radius-normal);
|
||||
color: var(--s-500);
|
||||
font-size: var(--font-size-normal);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
color: var(--s-500);
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-size: var(--font-size-mini);
|
||||
margin-top: var(--space-smaller);
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
|
@ -1,175 +0,0 @@
|
|||
<template>
|
||||
<woot-modal
|
||||
class="message-search--modal"
|
||||
:show.sync="show"
|
||||
:on-close="onClose"
|
||||
>
|
||||
<woot-modal-header :header-title="$t('CONVERSATION.SEARCH.TITLE')" />
|
||||
<div class="search--container">
|
||||
<input
|
||||
v-model="searchTerm"
|
||||
v-focus
|
||||
:placeholder="$t('CONVERSATION.SEARCH.PLACEHOLDER')"
|
||||
type="text"
|
||||
/>
|
||||
|
||||
<div v-if="uiFlags.isFetching" class="search--activity-message">
|
||||
<woot-spinner size="" />
|
||||
{{ $t('CONVERSATION.SEARCH.LOADING_MESSAGE') }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="searchTerm && conversations.length && !uiFlags.isFetching"
|
||||
class="search-results--container"
|
||||
>
|
||||
<div v-for="conversation in conversations" :key="conversation.id">
|
||||
<button
|
||||
v-for="message in conversation.messages"
|
||||
:key="message.id"
|
||||
class="search--messages"
|
||||
@click="() => onClick(conversation)"
|
||||
>
|
||||
<div class="search--messages__metadata">
|
||||
<span>#{{ conversation.id }}</span>
|
||||
<span>{{ dynamicTime(message.created_at) }}</span>
|
||||
</div>
|
||||
<div v-html="prepareContent(message.content)" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="
|
||||
searchTerm &&
|
||||
!conversations.length &&
|
||||
!uiFlags.isFetching &&
|
||||
hasSearched
|
||||
"
|
||||
class="search--activity-message"
|
||||
>
|
||||
{{ $t('CONVERSATION.SEARCH.NO_MATCHING_RESULTS') }}
|
||||
</div>
|
||||
</div>
|
||||
</woot-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { frontendURL, conversationUrl } from '../../../../helper/URLHelper';
|
||||
import timeMixin from '../../../../mixins/time';
|
||||
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
focus: {
|
||||
inserted(el) {
|
||||
el.focus();
|
||||
},
|
||||
},
|
||||
},
|
||||
mixins: [timeMixin, messageFormatterMixin],
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
onClose: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchTerm: '',
|
||||
hasSearched: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
conversations: 'conversationSearch/getConversations',
|
||||
uiFlags: 'conversationSearch/getUIFlags',
|
||||
accountId: 'getCurrentAccountId',
|
||||
}),
|
||||
},
|
||||
watch: {
|
||||
searchTerm(newValue) {
|
||||
if (this.typingTimer) {
|
||||
clearTimeout(this.typingTimer);
|
||||
}
|
||||
|
||||
this.typingTimer = setTimeout(() => {
|
||||
this.hasSearched = true;
|
||||
this.$store.dispatch('conversationSearch/get', { q: newValue });
|
||||
}, 1000);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('conversationSearch/get', { q: '' });
|
||||
},
|
||||
methods: {
|
||||
prepareContent(content = '') {
|
||||
const plainTextContent = this.getPlainText(content);
|
||||
return plainTextContent.replace(
|
||||
new RegExp(`(${this.searchTerm})`, 'ig'),
|
||||
'<span class="searchkey--highlight">$1</span>'
|
||||
);
|
||||
},
|
||||
onClick(conversation) {
|
||||
const path = conversationUrl({
|
||||
accountId: this.accountId,
|
||||
id: conversation.id,
|
||||
});
|
||||
window.location = frontendURL(path);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.search--container {
|
||||
font-size: var(--font-size-default);
|
||||
padding: var(--space-normal) var(--space-large);
|
||||
}
|
||||
|
||||
.search-results--container {
|
||||
max-height: 300px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.searchkey--highlight {
|
||||
background: var(--w-500);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.search--activity-message {
|
||||
color: var(--s-800);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.search--messages {
|
||||
border-bottom: 1px solid var(--b-100);
|
||||
color: var(--color-body);
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-small);
|
||||
line-height: 1.5;
|
||||
padding: var(--space-normal);
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
background: var(--w-50);
|
||||
}
|
||||
}
|
||||
|
||||
.message-search--modal .modal-container {
|
||||
width: 800px;
|
||||
min-height: 460px;
|
||||
}
|
||||
|
||||
.search--messages__metadata {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--space-small);
|
||||
color: var(--s-500);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,155 @@
|
|||
<template>
|
||||
<div class="message-item">
|
||||
<div class="search-message">
|
||||
<div class="user-wrap">
|
||||
<div class="name-wrap">
|
||||
<span class="user-name">{{ userName }}</span>
|
||||
<div>
|
||||
<i v-if="isOutgoingMessage" class="ion-headphone" />
|
||||
</div>
|
||||
</div>
|
||||
<span class="timestamp">{{ readableTime }} </span>
|
||||
</div>
|
||||
<p class="message-content" v-html="prepareContent(content)"></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { MESSAGE_TYPE } from 'shared/constants/messages';
|
||||
import timeMixin from 'dashboard/mixins/time';
|
||||
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||
|
||||
export default {
|
||||
mixins: [timeMixin, messageFormatterMixin],
|
||||
|
||||
props: {
|
||||
userName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
timestamp: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
messageType: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
searchTerm: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
isOutgoingMessage() {
|
||||
return this.messageType === MESSAGE_TYPE.OUTGOING;
|
||||
},
|
||||
readableTime() {
|
||||
if (!this.timestamp) {
|
||||
return '';
|
||||
}
|
||||
return this.dynamicTime(this.timestamp);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
prepareContent(content = '') {
|
||||
const plainTextContent = this.getPlainText(content);
|
||||
return plainTextContent.replace(
|
||||
new RegExp(`(${this.searchTerm})`, 'ig'),
|
||||
'<span class="searchkey--highlight">$1</span>'
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.message-item {
|
||||
background: var(--color-background-light);
|
||||
border-radius: var(--border-radius-medium);
|
||||
color: var(--color-body);
|
||||
margin-bottom: var(--space-small);
|
||||
margin-left: var(--space-one);
|
||||
padding: 0 var(--space-small);
|
||||
|
||||
&:hover {
|
||||
background: var(--w-400);
|
||||
color: var(--white);
|
||||
.message-content::v-deep .searchkey--highlight {
|
||||
color: var(--white);
|
||||
text-decoration: underline;
|
||||
}
|
||||
.ion-headphone {
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
&:last-child {
|
||||
.search-message {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-message {
|
||||
padding: var(--space-smaller) var(--space-smaller);
|
||||
&:hover {
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
|
||||
.user-wrap {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.name-wrap {
|
||||
display: flex;
|
||||
width: 22rem;
|
||||
|
||||
.user-name {
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-bold);
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.ion-headphone {
|
||||
color: var(--w-500);
|
||||
font-size: var(--font-size-mini);
|
||||
padding: var(--space-micro);
|
||||
padding-right: var(--space-smaller);
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
font-size: var(--font-size-mini);
|
||||
top: var(--space-micro);
|
||||
position: relative;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
p {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
font-size: var(--font-size-small);
|
||||
margin-bottom: var(--space-micro);
|
||||
padding: 0;
|
||||
line-height: 1.35;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.message-content::v-deep .searchkey--highlight {
|
||||
color: var(--w-600);
|
||||
font-weight: var(--font-weight-bold);
|
||||
font-size: var(--font-size-small);
|
||||
padding: (var(--space-zero) var(--space-zero));
|
||||
}
|
||||
</style>
|
|
@ -6,9 +6,22 @@ end
|
|||
json.payload do
|
||||
json.array! @conversations do |conversation|
|
||||
json.id conversation.display_id
|
||||
json.created_at conversation.created_at.to_i
|
||||
json.contact do
|
||||
json.id conversation.contact.id
|
||||
json.name conversation.contact.name
|
||||
end
|
||||
json.inbox do
|
||||
json.id conversation.inbox.id
|
||||
json.name conversation.inbox.name
|
||||
json.channel_type conversation.inbox.channel_type
|
||||
end
|
||||
json.messages do
|
||||
json.array! conversation.messages do |message|
|
||||
json.content message.content
|
||||
json.id message.id
|
||||
json.sender_name message.sender.name if message.sender
|
||||
json.message_type message.message_type_before_type_cast
|
||||
json.created_at message.created_at.to_i
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue