Compare commits
27
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be29211f5b | ||
|
|
55918c0bde | ||
|
|
4d2e43436b | ||
|
|
243188b9fe | ||
|
|
acae88b27a | ||
|
|
cadd701d74 | ||
|
|
6985dacc34 | ||
|
|
db5a5f1a03 | ||
|
|
f7221df333 | ||
|
|
e6896dc359 | ||
|
|
52f139561e | ||
|
|
f509a734f9 | ||
|
|
e61f5a4e62 | ||
|
|
33cdd2c0ed | ||
|
|
a607bf4b60 | ||
|
|
7added2d48 | ||
|
|
d3dd20e4b9 | ||
|
|
8db377d29b | ||
|
|
603c45a907 | ||
|
|
1dcaec1186 | ||
|
|
d26959f57c | ||
|
|
c114cc7fa0 | ||
|
|
7abe5b01dc | ||
|
|
047f1f6bc3 | ||
|
|
86ea2bbbe3 | ||
|
|
53ac68f0c0 | ||
|
|
a2af5e25e1 |
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="conversation flex flex-shrink-0 flex-grow-0 w-auto max-w-full cursor-pointer relative py-0 px-4 border-transparent border-l-2 border-t-0 border-b-0 border-r-0 border-solid items-start hover:bg-slate-25 dark:hover:bg-slate-800 group"
|
class="relative flex items-start flex-grow-0 flex-shrink-0 w-auto max-w-full px-4 py-0 border-t-0 border-b-0 border-l-2 border-r-0 border-transparent border-solid cursor-pointer conversation hover:bg-slate-25 dark:hover:bg-slate-800 group"
|
||||||
:class="{
|
:class="{
|
||||||
'active bg-slate-25 dark:bg-slate-800 border-woot-500': isActiveChat,
|
'active bg-slate-25 dark:bg-slate-800 border-woot-500': isActiveChat,
|
||||||
'unread-chat': hasUnread,
|
'unread-chat': hasUnread,
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
size="40px"
|
size="40px"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="py-3 px-0 border-b group-last:border-transparent group-hover:border-transparent border-slate-50 dark:border-slate-800/75 columns"
|
class="px-0 py-3 border-b group-last:border-transparent group-hover:border-transparent border-slate-50 dark:border-slate-800/75 columns"
|
||||||
>
|
>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<inbox-name v-if="showInboxName" :inbox="inbox" />
|
<inbox-name v-if="showInboxName" :inbox="inbox" />
|
||||||
@@ -55,44 +55,11 @@
|
|||||||
>
|
>
|
||||||
{{ currentContact.name }}
|
{{ currentContact.name }}
|
||||||
</h4>
|
</h4>
|
||||||
<p
|
<message-preview
|
||||||
v-if="lastMessageInChat"
|
v-if="lastMessageInChat"
|
||||||
class="conversation--message text-slate-700 dark:text-slate-200 text-sm my-0 mx-2 leading-6 h-6 max-w-[96%] w-[16.875rem] overflow-hidden text-ellipsis whitespace-nowrap"
|
:message="lastMessageInChat"
|
||||||
>
|
class="conversation--message mt-0 mb-2 mx-2 leading-6 h-6 max-w-[96%] w-[16.875rem] text-sm text-slate-700 dark:text-slate-200"
|
||||||
<fluent-icon
|
/>
|
||||||
v-if="isMessagePrivate"
|
|
||||||
size="16"
|
|
||||||
class="-mt-0.5 align-middle text-slate-600 dark:text-slate-300 inline-block"
|
|
||||||
icon="lock-closed"
|
|
||||||
/>
|
|
||||||
<fluent-icon
|
|
||||||
v-else-if="messageByAgent"
|
|
||||||
size="16"
|
|
||||||
class="-mt-0.5 align-middle text-slate-600 dark:text-slate-300 inline-block"
|
|
||||||
icon="arrow-reply"
|
|
||||||
/>
|
|
||||||
<fluent-icon
|
|
||||||
v-else-if="isMessageAnActivity"
|
|
||||||
size="16"
|
|
||||||
class="-mt-0.5 align-middle text-slate-600 dark:text-slate-300 inline-block"
|
|
||||||
icon="info"
|
|
||||||
/>
|
|
||||||
<span v-if="lastMessageInChat.content">
|
|
||||||
{{ parsedLastMessage }}
|
|
||||||
</span>
|
|
||||||
<span v-else-if="lastMessageInChat.attachments">
|
|
||||||
<fluent-icon
|
|
||||||
v-if="attachmentIcon"
|
|
||||||
size="16"
|
|
||||||
class="-mt-0.5 align-middle inline-block text-slate-600 dark:text-slate-300"
|
|
||||||
:icon="attachmentIcon"
|
|
||||||
/>
|
|
||||||
{{ $t(`${attachmentMessageContent}`) }}
|
|
||||||
</span>
|
|
||||||
<span v-else>
|
|
||||||
{{ $t('CHAT_LIST.NO_CONTENT') }}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<p
|
<p
|
||||||
v-else
|
v-else
|
||||||
class="conversation--message text-slate-700 dark:text-slate-200 text-sm my-0 mx-2 leading-6 h-6 max-w-[96%] w-[16.875rem] overflow-hidden text-ellipsis whitespace-nowrap"
|
class="conversation--message text-slate-700 dark:text-slate-200 text-sm my-0 mx-2 leading-6 h-6 max-w-[96%] w-[16.875rem] overflow-hidden text-ellipsis whitespace-nowrap"
|
||||||
@@ -106,8 +73,8 @@
|
|||||||
{{ $t(`CHAT_LIST.NO_MESSAGES`) }}
|
{{ $t(`CHAT_LIST.NO_MESSAGES`) }}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<div class="conversation--meta flex flex-col absolute right-4 top-4">
|
<div class="absolute flex flex-col conversation--meta right-4 top-4">
|
||||||
<span class="text-black-600 text-xxs font-normal leading-4 ml-auto">
|
<span class="ml-auto font-normal leading-4 text-black-600 text-xxs">
|
||||||
<time-ago
|
<time-ago
|
||||||
:last-activity-timestamp="chat.timestamp"
|
:last-activity-timestamp="chat.timestamp"
|
||||||
:created-at-timestamp="chat.created_at"
|
:created-at-timestamp="chat.created_at"
|
||||||
@@ -145,9 +112,8 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
|
||||||
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
|
||||||
import Thumbnail from '../Thumbnail.vue';
|
import Thumbnail from '../Thumbnail.vue';
|
||||||
|
import MessagePreview from './MessagePreview.vue';
|
||||||
import conversationMixin from '../../../mixins/conversations';
|
import conversationMixin from '../../../mixins/conversations';
|
||||||
import timeMixin from '../../../mixins/time';
|
import timeMixin from '../../../mixins/time';
|
||||||
import router from '../../../routes';
|
import router from '../../../routes';
|
||||||
@@ -159,14 +125,6 @@ import alertMixin from 'shared/mixins/alertMixin';
|
|||||||
import TimeAgo from 'dashboard/components/ui/TimeAgo.vue';
|
import TimeAgo from 'dashboard/components/ui/TimeAgo.vue';
|
||||||
import CardLabels from './conversationCardComponents/CardLabels.vue';
|
import CardLabels from './conversationCardComponents/CardLabels.vue';
|
||||||
import PriorityMark from './PriorityMark.vue';
|
import PriorityMark from './PriorityMark.vue';
|
||||||
const ATTACHMENT_ICONS = {
|
|
||||||
image: 'image',
|
|
||||||
audio: 'headphones-sound-wave',
|
|
||||||
video: 'video',
|
|
||||||
file: 'document',
|
|
||||||
location: 'location',
|
|
||||||
fallback: 'link',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -175,16 +133,11 @@ export default {
|
|||||||
Thumbnail,
|
Thumbnail,
|
||||||
ConversationContextMenu,
|
ConversationContextMenu,
|
||||||
TimeAgo,
|
TimeAgo,
|
||||||
|
MessagePreview,
|
||||||
PriorityMark,
|
PriorityMark,
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [
|
mixins: [inboxMixin, timeMixin, conversationMixin, alertMixin],
|
||||||
inboxMixin,
|
|
||||||
timeMixin,
|
|
||||||
conversationMixin,
|
|
||||||
messageFormatterMixin,
|
|
||||||
alertMixin,
|
|
||||||
],
|
|
||||||
props: {
|
props: {
|
||||||
activeLabel: {
|
activeLabel: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -258,20 +211,6 @@ export default {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
lastMessageFileType() {
|
|
||||||
const lastMessage = this.lastMessageInChat;
|
|
||||||
const [{ file_type: fileType } = {}] = lastMessage.attachments;
|
|
||||||
return fileType;
|
|
||||||
},
|
|
||||||
|
|
||||||
attachmentIcon() {
|
|
||||||
return ATTACHMENT_ICONS[this.lastMessageFileType];
|
|
||||||
},
|
|
||||||
|
|
||||||
attachmentMessageContent() {
|
|
||||||
return `CHAT_LIST.ATTACHMENTS.${this.lastMessageFileType}.CONTENT`;
|
|
||||||
},
|
|
||||||
|
|
||||||
isActiveChat() {
|
isActiveChat() {
|
||||||
return this.currentChat.id === this.chat.id;
|
return this.currentChat.id === this.chat.id;
|
||||||
},
|
},
|
||||||
@@ -292,30 +231,6 @@ export default {
|
|||||||
return this.lastMessage(this.chat);
|
return this.lastMessage(this.chat);
|
||||||
},
|
},
|
||||||
|
|
||||||
messageByAgent() {
|
|
||||||
const lastMessage = this.lastMessageInChat;
|
|
||||||
const { message_type: messageType } = lastMessage;
|
|
||||||
return messageType === MESSAGE_TYPE.OUTGOING;
|
|
||||||
},
|
|
||||||
|
|
||||||
isMessageAnActivity() {
|
|
||||||
const lastMessage = this.lastMessageInChat;
|
|
||||||
const { message_type: messageType } = lastMessage;
|
|
||||||
return messageType === MESSAGE_TYPE.ACTIVITY;
|
|
||||||
},
|
|
||||||
|
|
||||||
isMessagePrivate() {
|
|
||||||
const lastMessage = this.lastMessageInChat;
|
|
||||||
const { private: isPrivate } = lastMessage;
|
|
||||||
return isPrivate;
|
|
||||||
},
|
|
||||||
|
|
||||||
parsedLastMessage() {
|
|
||||||
const { content_attributes: contentAttributes } = this.lastMessageInChat;
|
|
||||||
const { email: { subject } = {} } = contentAttributes || {};
|
|
||||||
return this.getPlainText(subject || this.lastMessageInChat.content);
|
|
||||||
},
|
|
||||||
|
|
||||||
inbox() {
|
inbox() {
|
||||||
const { inbox_id: inboxId } = this.chat;
|
const { inbox_id: inboxId } = this.chat;
|
||||||
const stateInbox = this.$store.getters['inboxes/getInbox'](inboxId);
|
const stateInbox = this.$store.getters['inboxes/getInbox'](inboxId);
|
||||||
|
|||||||
@@ -34,6 +34,11 @@
|
|||||||
:url="storyUrl"
|
:url="storyUrl"
|
||||||
/>
|
/>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
|
<bubble-reply-to
|
||||||
|
v-if="inReplyToMessageId && inboxSupportsReplyTo"
|
||||||
|
:message="inReplyTo"
|
||||||
|
@click="navigateToMessage"
|
||||||
|
/>
|
||||||
<bubble-text
|
<bubble-text
|
||||||
v-if="data.content"
|
v-if="data.content"
|
||||||
:message="message"
|
:message="message"
|
||||||
@@ -141,6 +146,7 @@ import BubbleLocation from './bubble/Location.vue';
|
|||||||
import BubbleMailHead from './bubble/MailHead.vue';
|
import BubbleMailHead from './bubble/MailHead.vue';
|
||||||
import BubbleText from './bubble/Text.vue';
|
import BubbleText from './bubble/Text.vue';
|
||||||
import BubbleContact from './bubble/Contact.vue';
|
import BubbleContact from './bubble/Contact.vue';
|
||||||
|
import BubbleReplyTo from './bubble/ReplyTo.vue';
|
||||||
import Spinner from 'shared/components/Spinner.vue';
|
import Spinner from 'shared/components/Spinner.vue';
|
||||||
import ContextMenu from 'dashboard/modules/conversations/components/MessageContextMenu.vue';
|
import ContextMenu from 'dashboard/modules/conversations/components/MessageContextMenu.vue';
|
||||||
import instagramImageErrorPlaceholder from './instagramImageErrorPlaceholder.vue';
|
import instagramImageErrorPlaceholder from './instagramImageErrorPlaceholder.vue';
|
||||||
@@ -165,6 +171,7 @@ export default {
|
|||||||
BubbleMailHead,
|
BubbleMailHead,
|
||||||
BubbleText,
|
BubbleText,
|
||||||
BubbleContact,
|
BubbleContact,
|
||||||
|
BubbleReplyTo,
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
Spinner,
|
Spinner,
|
||||||
instagramImageErrorPlaceholder,
|
instagramImageErrorPlaceholder,
|
||||||
@@ -175,6 +182,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
currentChat: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
isATweet: {
|
isATweet: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@@ -271,6 +282,21 @@ export default {
|
|||||||
) + botMessageContent
|
) + botMessageContent
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
inReplyToMessageId() {
|
||||||
|
return this.data.content_attributes?.in_reply_to;
|
||||||
|
},
|
||||||
|
inReplyTo() {
|
||||||
|
if (!this.inReplyToMessageId) return null;
|
||||||
|
|
||||||
|
const replyTo = this.currentChat?.messages.find(message => {
|
||||||
|
if (message.id === this.inReplyToMessageId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return replyTo ?? {};
|
||||||
|
},
|
||||||
contextMenuEnabledOptions() {
|
contextMenuEnabledOptions() {
|
||||||
return {
|
return {
|
||||||
copy: this.hasText,
|
copy: this.hasText,
|
||||||
@@ -522,6 +548,13 @@ export default {
|
|||||||
this.showBackgroundHighlight = false;
|
this.showBackgroundHighlight = false;
|
||||||
}, HIGHLIGHT_TIMER);
|
}, HIGHLIGHT_TIMER);
|
||||||
},
|
},
|
||||||
|
navigateToMessage() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
bus.$emit(BUS_EVENTS.SCROLL_TO_MESSAGE, {
|
||||||
|
messageId: this.inReplyToMessageId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<span class="overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
|
<template v-if="showMessageType">
|
||||||
|
<fluent-icon
|
||||||
|
v-if="isMessagePrivate"
|
||||||
|
size="16"
|
||||||
|
class="-mt-0.5 align-middle text-slate-600 dark:text-slate-300 inline-block"
|
||||||
|
icon="lock-closed"
|
||||||
|
/>
|
||||||
|
<fluent-icon
|
||||||
|
v-else-if="messageByAgent"
|
||||||
|
size="16"
|
||||||
|
class="-mt-0.5 align-middle text-slate-600 dark:text-slate-300 inline-block"
|
||||||
|
icon="arrow-reply"
|
||||||
|
/>
|
||||||
|
<fluent-icon
|
||||||
|
v-else-if="isMessageAnActivity"
|
||||||
|
size="16"
|
||||||
|
class="-mt-0.5 align-middle text-slate-600 dark:text-slate-300 inline-block"
|
||||||
|
icon="info"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<span v-if="message.content">
|
||||||
|
{{ parsedLastMessage }}
|
||||||
|
</span>
|
||||||
|
<span v-else-if="message.attachments">
|
||||||
|
<fluent-icon
|
||||||
|
v-if="attachmentIcon && showMessageType"
|
||||||
|
size="16"
|
||||||
|
class="-mt-0.5 align-middle inline-block text-slate-600 dark:text-slate-300"
|
||||||
|
:icon="attachmentIcon"
|
||||||
|
/>
|
||||||
|
{{ $t(`${attachmentMessageContent}`) }}
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
{{ defaultEmptyMessage || $t('CHAT_LIST.NO_CONTENT') }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
||||||
|
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||||
|
import { ATTACHMENT_ICONS } from 'shared/constants/messages';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'MessagePreview',
|
||||||
|
mixins: [messageFormatterMixin],
|
||||||
|
props: {
|
||||||
|
message: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
showMessageType: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
defaultEmptyMessage: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
messageByAgent() {
|
||||||
|
const { message_type: messageType } = this.message;
|
||||||
|
return messageType === MESSAGE_TYPE.OUTGOING;
|
||||||
|
},
|
||||||
|
isMessageAnActivity() {
|
||||||
|
const { message_type: messageType } = this.message;
|
||||||
|
return messageType === MESSAGE_TYPE.ACTIVITY;
|
||||||
|
},
|
||||||
|
isMessagePrivate() {
|
||||||
|
const { private: isPrivate } = this.message;
|
||||||
|
return isPrivate;
|
||||||
|
},
|
||||||
|
parsedLastMessage() {
|
||||||
|
const { content_attributes: contentAttributes } = this.message;
|
||||||
|
const { email: { subject } = {} } = contentAttributes || {};
|
||||||
|
return this.getPlainText(subject || this.message.content);
|
||||||
|
},
|
||||||
|
lastMessageFileType() {
|
||||||
|
const [{ file_type: fileType } = {}] = this.message.attachments;
|
||||||
|
return fileType;
|
||||||
|
},
|
||||||
|
attachmentIcon() {
|
||||||
|
return ATTACHMENT_ICONS[this.lastMessageFileType];
|
||||||
|
},
|
||||||
|
attachmentMessageContent() {
|
||||||
|
return `CHAT_LIST.ATTACHMENTS.${this.lastMessageFileType}.CONTENT`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
:has-instagram-story="hasInstagramStory"
|
:has-instagram-story="hasInstagramStory"
|
||||||
:is-web-widget-inbox="isAWebWidgetInbox"
|
:is-web-widget-inbox="isAWebWidgetInbox"
|
||||||
:inbox-supports-reply-to="inboxSupportsReplyTo"
|
:inbox-supports-reply-to="inboxSupportsReplyTo"
|
||||||
|
:current-chat="currentChat"
|
||||||
/>
|
/>
|
||||||
<li v-show="unreadMessageCount != 0" class="unread--toast">
|
<li v-show="unreadMessageCount != 0" class="unread--toast">
|
||||||
<span>
|
<span>
|
||||||
@@ -55,6 +56,8 @@
|
|||||||
:is-a-whatsapp-channel="isAWhatsAppChannel"
|
:is-a-whatsapp-channel="isAWhatsAppChannel"
|
||||||
:has-instagram-story="hasInstagramStory"
|
:has-instagram-story="hasInstagramStory"
|
||||||
:is-web-widget-inbox="isAWebWidgetInbox"
|
:is-web-widget-inbox="isAWebWidgetInbox"
|
||||||
|
:inbox-supports-reply-to="inboxSupportsReplyTo"
|
||||||
|
:current-chat="currentChat"
|
||||||
/>
|
/>
|
||||||
<conversation-label-suggestion
|
<conversation-label-suggestion
|
||||||
v-if="shouldShowLabelSuggestions"
|
v-if="shouldShowLabelSuggestions"
|
||||||
|
|||||||
@@ -21,8 +21,7 @@
|
|||||||
<div class="reply-box__top">
|
<div class="reply-box__top">
|
||||||
<reply-to-message
|
<reply-to-message
|
||||||
v-if="shouldShowReplyToMessage"
|
v-if="shouldShowReplyToMessage"
|
||||||
:message-id="inReplyTo.id"
|
:message="inReplyTo"
|
||||||
:message-content="inReplyTo.content"
|
|
||||||
@dismiss="resetReplyToMessage"
|
@dismiss="resetReplyToMessage"
|
||||||
@navigate-to-message="navigateToMessage"
|
@navigate-to-message="navigateToMessage"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,29 +1,12 @@
|
|||||||
<script setup>
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import { extractTextFromMarkdown } from 'dashboard/helper/editorHelper';
|
|
||||||
|
|
||||||
const { messageContent } = defineProps({
|
|
||||||
messageId: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
messageContent: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const cleanedContent = computed(() => extractTextFromMarkdown(messageContent));
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="reply-editor bg-slate-50 rounded-md py-1 pl-2 pr-1 text-xs tracking-wide mt-2 flex items-center gap-1.5 -mx-2 cursor-pointer"
|
class="reply-editor bg-slate-50 dark:bg-slate-800 rounded-md py-1 pl-2 pr-1 text-xs tracking-wide mt-2 flex items-center gap-1.5 -mx-2 cursor-pointer"
|
||||||
@click="$emit('navigate-to-message', messageId)"
|
@click="$emit('navigate-to-message', messageId)"
|
||||||
>
|
>
|
||||||
<fluent-icon class="flex-shrink-0 icon" icon="arrow-reply" icon-size="14" />
|
<fluent-icon class="flex-shrink-0 icon" icon="arrow-reply" icon-size="14" />
|
||||||
<div class="flex-grow overflow-hidden text-ellipsis">
|
<div class="flex items-center flex-grow gap-1 text-xs">
|
||||||
{{ $t('CONVERSATION.REPLYBOX.REPLYING_TO') }} {{ cleanedContent }}.
|
{{ $t('CONVERSATION.REPLYBOX.REPLYING_TO') }}
|
||||||
|
<message-preview :message="message" :show-message-type="false" />
|
||||||
</div>
|
</div>
|
||||||
<woot-button
|
<woot-button
|
||||||
v-tooltip="$t('CONVERSATION.REPLYBOX.DISMISS_REPLY')"
|
v-tooltip="$t('CONVERSATION.REPLYBOX.DISMISS_REPLY')"
|
||||||
@@ -37,6 +20,20 @@ const cleanedContent = computed(() => extractTextFromMarkdown(messageContent));
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import MessagePreview from 'dashboard/components/widgets/conversation/MessagePreview.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { MessagePreview },
|
||||||
|
props: {
|
||||||
|
message: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
// TODO: Remove this
|
// TODO: Remove this
|
||||||
// override for dashboard/assets/scss/widgets/_reply-box.scss
|
// override for dashboard/assets/scss/widgets/_reply-box.scss
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="px-2 py-1.5 -mx-2 rounded-md bg-woot-600 text-woot-50 min-w-[15rem] mb-2 cursor-pointer"
|
||||||
|
@click="$emit('click')"
|
||||||
|
>
|
||||||
|
<message-preview
|
||||||
|
:message="message"
|
||||||
|
:show-message-type="false"
|
||||||
|
::default-empty-message="$t('CONVERSATION.REPLY_MESSAGE_NOT_FOUND')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import MessagePreview from 'dashboard/components/widgets/conversation/MessagePreview.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ReplyTo',
|
||||||
|
components: {
|
||||||
|
MessagePreview,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
message: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -46,6 +46,12 @@
|
|||||||
"NO_RESPONSE": "No response",
|
"NO_RESPONSE": "No response",
|
||||||
"RATING_TITLE": "Rating",
|
"RATING_TITLE": "Rating",
|
||||||
"FEEDBACK_TITLE": "Feedback",
|
"FEEDBACK_TITLE": "Feedback",
|
||||||
|
"REPLY_MESSAGE_NOT_FOUND": "Not available, click to scroll to the message",
|
||||||
|
"REPLY_TO_ATTACHMENT": {
|
||||||
|
"FILE_PLUS_MULTIPLE": "%{first} and %{count} others",
|
||||||
|
"COUNT": "%{count} files",
|
||||||
|
"ATTACHMENT": "Attachment"
|
||||||
|
},
|
||||||
"CARD": {
|
"CARD": {
|
||||||
"SHOW_LABELS": "Show labels",
|
"SHOW_LABELS": "Show labels",
|
||||||
"HIDE_LABELS": "Hide labels"
|
"HIDE_LABELS": "Hide labels"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"arrow-clockwise-outline": "M12 4.75a7.25 7.25 0 1 0 7.201 6.406c-.068-.588.358-1.156.95-1.156.515 0 .968.358 1.03.87a9.25 9.25 0 1 1-3.432-6.116V4.25a1 1 0 1 1 2.001 0v2.698l.034.052h-.034v.25a1 1 0 0 1-1 1h-3a1 1 0 1 1 0-2h.666A7.219 7.219 0 0 0 12 4.75Z",
|
"arrow-clockwise-outline": "M12 4.75a7.25 7.25 0 1 0 7.201 6.406c-.068-.588.358-1.156.95-1.156.515 0 .968.358 1.03.87a9.25 9.25 0 1 1-3.432-6.116V4.25a1 1 0 1 1 2.001 0v2.698l.034.052h-.034v.25a1 1 0 0 1-1 1h-3a1 1 0 1 1 0-2h.666A7.219 7.219 0 0 0 12 4.75Z",
|
||||||
"arrow-right-outline": "M13.267 4.209a.75.75 0 0 0-1.034 1.086l6.251 5.955H3.75a.75.75 0 0 0 0 1.5h14.734l-6.251 5.954a.75.75 0 0 0 1.034 1.087l7.42-7.067a.996.996 0 0 0 .3-.58.758.758 0 0 0-.001-.29.995.995 0 0 0-.3-.578l-7.419-7.067Z",
|
"arrow-right-outline": "M13.267 4.209a.75.75 0 0 0-1.034 1.086l6.251 5.955H3.75a.75.75 0 0 0 0 1.5h14.734l-6.251 5.954a.75.75 0 0 0 1.034 1.087l7.42-7.067a.996.996 0 0 0 .3-.58.758.758 0 0 0-.001-.29.995.995 0 0 0-.3-.578l-7.419-7.067Z",
|
||||||
|
"arrow-reply-outline": "M9.277 16.221a.75.75 0 0 1-1.061 1.06l-4.997-5.003a.75.75 0 0 1 0-1.06L8.217 6.22a.75.75 0 0 1 1.061 1.06L5.557 11h7.842c1.595 0 2.81.242 3.889.764l.246.126a6.203 6.203 0 0 1 2.576 2.576c.61 1.14.89 2.418.89 4.135a.75.75 0 0 1-1.5 0c0-1.484-.228-2.52-.713-3.428a4.702 4.702 0 0 0-1.96-1.96c-.838-.448-1.786-.676-3.094-.709L13.4 12.5H5.562l3.715 3.721Z",
|
||||||
"attach-outline": "M11.772 3.743a6 6 0 0 1 8.66 8.302l-.19.197-8.8 8.798-.036.03a3.723 3.723 0 0 1-5.489-4.973.764.764 0 0 1 .085-.13l.054-.06.086-.088.142-.148.002.003 7.436-7.454a.75.75 0 0 1 .977-.074l.084.073a.75.75 0 0 1 .074.976l-.073.084-7.594 7.613a2.23 2.23 0 0 0 3.174 3.106l8.832-8.83A4.502 4.502 0 0 0 13 4.644l-.168.16-.013.014-9.536 9.536a.75.75 0 0 1-1.133-.977l.072-.084 9.549-9.55h.002Z",
|
"attach-outline": "M11.772 3.743a6 6 0 0 1 8.66 8.302l-.19.197-8.8 8.798-.036.03a3.723 3.723 0 0 1-5.489-4.973.764.764 0 0 1 .085-.13l.054-.06.086-.088.142-.148.002.003 7.436-7.454a.75.75 0 0 1 .977-.074l.084.073a.75.75 0 0 1 .074.976l-.073.084-7.594 7.613a2.23 2.23 0 0 0 3.174 3.106l8.832-8.83A4.502 4.502 0 0 0 13 4.644l-.168.16-.013.014-9.536 9.536a.75.75 0 0 1-1.133-.977l.072-.084 9.549-9.55h.002Z",
|
||||||
"chat-outline": "M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10a9.96 9.96 0 0 1-4.587-1.112l-3.826 1.067a1.25 1.25 0 0 1-1.54-1.54l1.068-3.823A9.96 9.96 0 0 1 2 12C2 6.477 6.477 2 12 2Zm0 1.5A8.5 8.5 0 0 0 3.5 12c0 1.47.373 2.883 1.073 4.137l.15.27-1.112 3.984 3.987-1.112.27.15A8.5 8.5 0 1 0 12 3.5ZM8.75 13h4.498a.75.75 0 0 1 .102 1.493l-.102.007H8.75a.75.75 0 0 1-.102-1.493L8.75 13h4.498H8.75Zm0-3.5h6.505a.75.75 0 0 1 .101 1.493l-.101.007H8.75a.75.75 0 0 1-.102-1.493L8.75 9.5h6.505H8.75Z",
|
"chat-outline": "M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10a9.96 9.96 0 0 1-4.587-1.112l-3.826 1.067a1.25 1.25 0 0 1-1.54-1.54l1.068-3.823A9.96 9.96 0 0 1 2 12C2 6.477 6.477 2 12 2Zm0 1.5A8.5 8.5 0 0 0 3.5 12c0 1.47.373 2.883 1.073 4.137l.15.27-1.112 3.984 3.987-1.112.27.15A8.5 8.5 0 1 0 12 3.5ZM8.75 13h4.498a.75.75 0 0 1 .102 1.493l-.102.007H8.75a.75.75 0 0 1-.102-1.493L8.75 13h4.498H8.75Zm0-3.5h6.505a.75.75 0 0 1 .101 1.493l-.101.007H8.75a.75.75 0 0 1-.102-1.493L8.75 9.5h6.505H8.75Z",
|
||||||
"checkmark-outline": "M4.53 12.97a.75.75 0 0 0-1.06 1.06l4.5 4.5a.75.75 0 0 0 1.06 0l11-11a.75.75 0 0 0-1.06-1.06L8.5 16.94l-3.97-3.97Z",
|
"checkmark-outline": "M4.53 12.97a.75.75 0 0 0-1.06 1.06l4.5 4.5a.75.75 0 0 0 1.06 0l11-11a.75.75 0 0 0-1.06-1.06L8.5 16.94l-3.97-3.97Z",
|
||||||
|
|||||||
@@ -146,3 +146,12 @@ export const MESSAGE_VARIABLES = [
|
|||||||
key: 'agent.email',
|
key: 'agent.email',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const ATTACHMENT_ICONS = {
|
||||||
|
image: 'image',
|
||||||
|
audio: 'headphones-sound-wave',
|
||||||
|
video: 'video',
|
||||||
|
file: 'document',
|
||||||
|
location: 'location',
|
||||||
|
fallback: 'link',
|
||||||
|
};
|
||||||
|
|||||||
@@ -13,6 +13,23 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="message-wrap">
|
<div class="message-wrap">
|
||||||
|
<div class="flex mb-1 text-xs">
|
||||||
|
<button
|
||||||
|
v-if="replyTo && (replyTo.content || replyTo.attachments)"
|
||||||
|
class="px-1.5 py-0.5 rounded-md text-slate-600 bg-slate-50 hover:bg-slate-100 hover:text-slate-800 cursor-pointer flex items-center gap-1.5"
|
||||||
|
@click="navigateTo(replyTo.id)"
|
||||||
|
>
|
||||||
|
<FluentIcon icon="arrow-reply" size="12" class="flex-shrink-0" />
|
||||||
|
<div
|
||||||
|
v-if="replyTo.content"
|
||||||
|
v-dompurify-html="formatMessage(replyTo.content, false)"
|
||||||
|
class="reply-to-truncate"
|
||||||
|
/>
|
||||||
|
<div v-else-if="replyTo.attachments" class="reply-to-truncate">
|
||||||
|
<p>{{ replyToAttachment }}</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<AgentMessageBubble
|
<AgentMessageBubble
|
||||||
v-if="shouldDisplayAgentMessage"
|
v-if="shouldDisplayAgentMessage"
|
||||||
:content-type="contentType"
|
:content-type="contentType"
|
||||||
@@ -72,6 +89,8 @@ import configMixin from '../mixins/configMixin';
|
|||||||
import messageMixin from '../mixins/messageMixin';
|
import messageMixin from '../mixins/messageMixin';
|
||||||
import { isASubmittedFormMessage } from 'shared/helpers/MessageTypeHelper';
|
import { isASubmittedFormMessage } from 'shared/helpers/MessageTypeHelper';
|
||||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||||
|
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||||
|
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AgentMessage',
|
name: 'AgentMessage',
|
||||||
@@ -81,13 +100,24 @@ export default {
|
|||||||
Thumbnail,
|
Thumbnail,
|
||||||
UserMessage,
|
UserMessage,
|
||||||
FileBubble,
|
FileBubble,
|
||||||
|
FluentIcon,
|
||||||
},
|
},
|
||||||
mixins: [timeMixin, configMixin, messageMixin, darkModeMixin],
|
mixins: [
|
||||||
|
timeMixin,
|
||||||
|
configMixin,
|
||||||
|
messageMixin,
|
||||||
|
darkModeMixin,
|
||||||
|
messageFormatterMixin,
|
||||||
|
],
|
||||||
props: {
|
props: {
|
||||||
message: {
|
message: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {},
|
default: () => {},
|
||||||
},
|
},
|
||||||
|
replyTo: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -180,6 +210,14 @@ export default {
|
|||||||
'has-text': this.shouldDisplayAgentMessage,
|
'has-text': this.shouldDisplayAgentMessage,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
replyToAttachment() {
|
||||||
|
if (!this.replyTo?.attachments.length) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const [{ file_type: fileType } = {}] = this.replyTo.attachments;
|
||||||
|
return this.$t(`ATTACHMENTS.${fileType}.CONTENT`);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
message() {
|
message() {
|
||||||
@@ -193,6 +231,18 @@ export default {
|
|||||||
onImageLoadError() {
|
onImageLoadError() {
|
||||||
this.hasImageError = true;
|
this.hasImageError = true;
|
||||||
},
|
},
|
||||||
|
navigateTo(id) {
|
||||||
|
const elementId = `cwmsg-${id}`;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
document.getElementById(elementId).scrollIntoView();
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.reply-to-truncate > p {
|
||||||
|
@apply overflow-hidden truncate max-w-[8rem];
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<UserMessage v-if="isUserMessage" :message="message" />
|
<UserMessage
|
||||||
<AgentMessage v-else :message="message" />
|
v-if="isUserMessage"
|
||||||
|
:id="`cwmsg-${message.id}`"
|
||||||
|
:message="message"
|
||||||
|
:reply-to="replyTo"
|
||||||
|
/>
|
||||||
|
<AgentMessage
|
||||||
|
v-else
|
||||||
|
:id="`cwmsg-${message.id}`"
|
||||||
|
:message="message"
|
||||||
|
:reply-to="replyTo"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import AgentMessage from 'widget/components/AgentMessage.vue';
|
import AgentMessage from 'widget/components/AgentMessage.vue';
|
||||||
import UserMessage from 'widget/components/UserMessage.vue';
|
import UserMessage from 'widget/components/UserMessage.vue';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -20,9 +31,16 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
allMessages: 'conversation/getConversation',
|
||||||
|
}),
|
||||||
isUserMessage() {
|
isUserMessage() {
|
||||||
return this.message.message_type === MESSAGE_TYPE.INCOMING;
|
return this.message.message_type === MESSAGE_TYPE.INCOMING;
|
||||||
},
|
},
|
||||||
|
replyTo() {
|
||||||
|
const replyTo = this.message?.content_attributes?.in_reply_to;
|
||||||
|
return replyTo ? this.allMessages[replyTo] : null;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user