Compare commits

..
Author SHA1 Message Date
Pranav 38861ee06a Fix eslint 2024-12-12 17:41:43 -08:00
Pranav 0bba0ef6ff Fix stories 2024-12-12 17:36:08 -08:00
Shivam MishraandGitHub f865a2fb38 Merge branch 'develop' into feat/message-bubble 2024-12-12 18:49:12 +05:30
Shivam Mishra 67aa85a508 feat: add private note example 2024-12-12 18:42:27 +05:30
Shivam Mishra 898a959755 fix: required 2024-12-12 17:57:59 +05:30
Shivam Mishra dabde6fad6 fix: types 2024-12-12 17:57:34 +05:30
Shivam Mishra 5caa24ec58 feat: add content types 2024-12-12 13:35:39 +05:30
Shivam Mishra 25d9588095 feat: update styles 2024-12-12 13:35:34 +05:30
Shivam Mishra 974a9f00b9 fix: error handler 2024-12-12 13:33:37 +05:30
Shivam Mishra ed8adb36fe fix: styles for base 2024-12-12 13:33:10 +05:30
Shivam Mishra 59d07f71e7 feat: use formatted content 2024-12-12 13:31:51 +05:30
Shivam Mishra 1f71733400 fix: meta 2024-12-12 13:29:02 +05:30
Shivam Mishra 873e67363d feat: add bubble names 2024-12-12 13:25:44 +05:30
Shivam Mishra c561cf596b refactor: split story 2024-12-11 18:32:11 +05:30
Shivam Mishra ad6864fd18 feat: add dyte bubble 2024-12-11 18:25:13 +05:30
Shivam Mishra 9807ed2fd7 fix: import 2024-12-11 17:55:51 +05:30
Shivam Mishra 0935adb61f feat: move stories 2024-12-11 17:55:09 +05:30
Shivam Mishra 245da198cb feat: add broken image chip example 2024-12-11 17:53:37 +05:30
Shivam Mishra 13c2a12865 fix: ig reel 2024-12-11 17:51:02 +05:30
Shivam Mishra f9ddb38019 fix: link 2024-12-11 17:49:17 +05:30
Shivam Mishra 789038ea7d fix: bubbles 2024-12-11 17:48:49 +05:30
Shivam Mishra 6ec955c277 feat: use data url instead of thumb url 2024-12-11 17:47:32 +05:30
Shivam Mishra f15e997806 fix: handle error 2024-12-11 17:47:22 +05:30
Shivam Mishra 40888b8a1d style: remove unused imports 2024-12-11 17:47:16 +05:30
Shivam Mishra 409e367c21 fix: link 2024-12-11 17:47:08 +05:30
Shivam Mishra 8089d4fe70 feat: update design for audio bubble 2024-12-11 17:42:09 +05:30
Shivam Mishra 414c6c7e97 fix: audio chip design 2024-12-11 17:41:33 +05:30
Shivam Mishra 71aeb2abb3 feat: add i18n 2024-12-11 17:41:03 +05:30
Shivam Mishra 777747b237 feat: update provider 2024-12-11 17:39:22 +05:30
Shivam Mishra 7f2b6019f9 feat: handle errors and gallery 2024-12-11 17:38:47 +05:30
Shivam Mishra 98f1dd46f4 feat: common base attachment component 2024-12-11 17:36:55 +05:30
Shivam Mishra 12d41eb694 feat: update file chip 2024-12-11 17:24:33 +05:30
Shivam Mishra c3268dcc4c feat: update location bubble 2024-12-11 17:19:14 +05:30
Shivam Mishra 7c34b5ce0a feat: update contact design 2024-12-11 17:11:07 +05:30
Shivam Mishra af01147221 fix: attachments 2024-12-11 16:58:54 +05:30
Shivam Mishra 4dbdb0bf0c fix: email spacing 2024-12-11 16:58:06 +05:30
Shivam Mishra 87699ee296 feat: better stories 2024-12-11 16:55:40 +05:30
Shivam Mishra a20852722c fix: color 2024-12-11 16:03:40 +05:30
Shivam Mishra bd3a2b481b fix: white alpha color 2024-12-11 16:03:38 +05:30
Shivam Mishra f349a802d5 fix: download color 2024-12-11 16:03:35 +05:30
Shivam Mishra b0824f3b67 fix: orientatation for chips 2024-12-11 16:03:31 +05:30
Shivam Mishra 1825989663 feat: add colors to text chip 2024-12-11 16:01:51 +05:30
Shivam Mishra 233cc8b868 feat: use woot-icons 2024-12-11 16:01:49 +05:30
Shivam Mishra ea87274d61 feat: move icons outside 2024-12-11 16:00:45 +05:30
Shivam Mishra 64ba5857e0 feat: use lucide 2024-12-11 16:00:32 +05:30
Shivam Mishra ed158716ba fix: sender avatar 2024-12-06 18:53:50 +05:30
Shivam Mishra 97ae755783 fix: attachments check 2024-12-06 18:51:42 +05:30
Shivam Mishra bcc03a79f7 feat: allow otions 2024-12-06 18:19:49 +05:30
Shivam Mishra 55952f5fe0 fix: pnpm 2024-12-06 18:19:43 +05:30
Shivam Mishra be01ab63f7 Merge branch 'develop' of github.com:chatwoot/chatwoot into feat/message-bubble 2024-12-06 18:11:07 +05:30
Shivam Mishra e7471b8e74 feat: add contact card 2024-12-06 14:36:24 +05:30
Shivam Mishra 9c6b78700c fix: location 2024-12-06 10:45:26 +05:30
Shivam Mishra 685cf56819 feat: add support for unsupported 2024-12-05 17:37:50 +05:30
Shivam Mishra e7fddee4dc feat: add location bubble 2024-12-05 17:13:57 +05:30
Shivam Mishra 26b5c2c136 feat: setup instagram 2024-12-05 15:08:37 +05:30
Shivam Mishra 68f02e06b7 feat: add fixtures for IG 2024-12-05 14:26:31 +05:30
Shivam Mishra c2021a86d2 feat: add ig bubble 2024-12-05 14:20:16 +05:30
Shivam Mishra d610d3e0bb fix: locale 2024-12-05 14:19:43 +05:30
Shivam Mishra 59d5c91977 feat: simpler expandable check 2024-12-04 19:51:55 +05:30
Shivam Mishra b78a272806 feat: better resize observer 2024-12-04 19:44:48 +05:30
Shivam Mishra 2456b90a75 feat: add quote extracter 2024-12-04 19:38:39 +05:30
Shivam Mishra 828a46e40b feat: use sender 2024-12-04 18:39:41 +05:30
Shivam Mishra 4739d45376 feat: seprate email meta component 2024-12-04 18:38:21 +05:30
Shivam Mishra 12c239228a feat: format email text content 2024-12-04 17:49:58 +05:30
Shivam Mishra 89956d006c feat: common attachment chips 2024-12-04 17:46:57 +05:30
Shivam Mishra b1e55bab9e fix: fullwidth 2024-12-04 17:39:44 +05:30
Shivam Mishra 89f95742f8 fix: styling 2024-12-04 17:33:36 +05:30
Shivam Mishra 95674eb601 feat: more attachments 2024-12-04 17:32:50 +05:30
Shivam Mishra 5c863906e0 feat: add attachments 2024-12-04 17:31:21 +05:30
Shivam Mishra e68f923a09 fix: first child margin 2024-12-04 17:31:17 +05:30
Shivam Mishra 30f368cbcd feat: better story 2024-12-04 17:24:02 +05:30
Shivam Mishra 0a3840501e feat: add error case 2024-12-04 17:08:06 +05:30
Shivam Mishra 1fc4445bba feat: better stories 2024-12-02 17:58:05 +05:30
Shivam Mishra 478589de91 feat: more fixtures 2024-12-02 17:57:59 +05:30
Shivam Mishra 8c98715c97 fix: border color 2024-12-02 17:48:56 +05:30
Shivam Mishra 26ee5eb37b feat: update font weight 2024-12-02 17:44:51 +05:30
Shivam Mishra 79f57c74a2 feat: remove margin from first heading 2024-12-02 17:44:10 +05:30
Shivam Mishra f6b07d094e feat: add simple email 2024-12-02 17:42:50 +05:30
Shivam Mishra 8528bd331e feat: add hr 2024-12-02 17:42:33 +05:30
Shivam Mishra 83f0b57d55 fix: css 2024-12-02 16:24:57 +05:30
Shivam Mishra 197bc946d4 feat: add email overflow 2024-12-02 15:51:03 +05:30
Shivam Mishra 27c051984d feat: add expanded view 2024-12-02 15:42:12 +05:30
Shivam Mishra 15540dd633 feat: add showMeta 2024-12-02 15:14:23 +05:30
Shivam Mishra 801b4a3406 feat: check if is-overflowing 2024-12-02 15:08:13 +05:30
Shivam Mishra e434d9bb5d feat: add email meta 2024-12-02 15:03:33 +05:30
Shivam Mishra 2a9498819a feat: email typography 2024-12-02 14:45:46 +05:30
Shivam Mishra 59481afc2c feat: setup email content 2024-11-29 16:45:52 +05:30
Shivam Mishra 03389ad773 feat: setup email bubble 2024-11-29 16:37:42 +05:30
Shivam Mishra 376c6ae90a feat: setup base for email 2024-11-29 16:37:42 +05:30
Shivam Mishra 9c5ee5940d feat: add padding for right message 2024-11-29 16:37:42 +05:30
Shivam Mishra c2ede6a3e9 feat: add check for sender 2024-11-29 16:37:42 +05:30
Shivam Mishra e1f8c905f8 fix: sender type fallback in isMyMessage 2024-11-29 16:37:42 +05:30
Shivam Mishra c2d0da234a refactor: rename story
feat: remove email
2024-11-29 16:37:39 +05:30
Shivam Mishra 781182905d feat: better download 2024-11-29 14:59:23 +05:30
Shivam Mishra c841349de3 feat: use better placeholder 2024-11-29 14:56:40 +05:30
Shivam Mishra 12900928be feat: use iris 2024-11-28 19:10:01 +05:30
Shivam Mishra 39b06a76ce feat: add inreplyto 2024-11-28 18:55:44 +05:30
Shivam Mishra ebe35c7843 feat: better examples 2024-11-28 18:34:24 +05:30
Shivam Mishra d73d941568 fix: spacing 2024-11-28 18:32:18 +05:30
Shivam Mishra b304e86f4f feat: add attachment only component 2024-11-28 18:27:55 +05:30
Shivam Mishra 44b4c7ce14 fix: colors 2024-11-28 18:09:41 +05:30
Shivam Mishra b1fd52c275 feat: add file chip 2024-11-28 18:06:55 +05:30
Shivam Mishra 82c46dbb9c fix: download link 2024-11-28 17:58:34 +05:30
Shivam Mishra b8248978d8 feat: use new file icon 2024-11-28 17:47:47 +05:30
Shivam Mishra e8cbb6d8e8 feat: update colors 2024-11-28 17:46:04 +05:30
Shivam Mishra a706f26ef4 fix: dark mode colors 2024-11-28 17:43:00 +05:30
Shivam Mishra 0d40f2a027 feat: make content optional 2024-11-28 17:40:19 +05:30
Shivam Mishra 600b06f748 feat: add audio bubble 2024-11-28 17:39:55 +05:30
Shivam Mishra 3bf8468077 feat: rename media to image 2024-11-28 17:19:07 +05:30
Shivam Mishra bcd18a0bb0 feat: add audio chip 2024-11-28 17:11:24 +05:30
Shivam Mishra a565ae8cba feat: add more data 2024-11-28 15:47:50 +05:30
Shivam Mishra 2274141d99 feat: add more examples 2024-11-28 15:40:08 +05:30
Shivam Mishra d9ef6df4de feat: add image and video chip 2024-11-28 15:40:05 +05:30
Shivam Mishra 820fce8bfc feat: update layout 2024-11-28 15:05:12 +05:30
Shivam Mishra bba2befcb7 fix: extra spacing 2024-11-27 20:19:48 +05:30
Shivam Mishra 59afa50b71 feat: add text images 2024-11-27 20:19:06 +05:30
Shivam Mishra c2305a11c6 fix: condition to render files 2024-11-27 20:08:27 +05:30
Shivam Mishra 36e777aea5 feat: file icon 2024-11-27 20:06:49 +05:30
Shivam Mishra ac7f2c7153 feat: add teenyicons for files 2024-11-27 20:05:58 +05:30
Shivam Mishra d93e2ee60a feat: add download option
TODO: Get better designs
2024-11-27 20:05:42 +05:30
Shivam Mishra 32df64c965 feat: add file icon 2024-11-27 20:05:11 +05:30
Shivam Mishra fd51a89b59 Merge branch 'develop' of github.com:chatwoot/chatwoot into feat/message-bubble 2024-11-27 19:05:23 +05:30
Shivam Mishra 226d7ceb03 feat: kickstart media bubble 2024-11-27 19:05:07 +05:30
Shivam Mishra 230279cafe feat: move padding to terminal bubble 2024-11-27 19:04:52 +05:30
Shivam Mishra a1479590e7 feat: add message id 2024-11-27 18:37:16 +05:30
Shivam Mishra ee71d6d50f feat: separate activity bubble 2024-11-27 18:35:09 +05:30
Shivam Mishra 0ae085ffbb feat: separate Base bubble
Also added a provider to inject local variant and orientation state to child components
2024-11-27 18:32:46 +05:30
Shivam Mishra 93bbcb2976 feat: capitalize sender name 2024-11-27 18:31:59 +05:30
Shivam Mishra 5a387689c7 fix: typo 2024-11-27 18:15:10 +05:30
Shivam Mishra 26501fc47c feat: add example for text with media 2024-11-27 18:14:07 +05:30
Shivam Mishra cffbc524d2 feat: move data to fixtures 2024-11-27 18:13:27 +05:30
Shivam Mishra da5a752c75 feat: add tooltip 2024-11-27 17:12:26 +05:30
Shivam Mishra 825e1b4fb3 feat: add i18n for error message 2024-11-27 17:09:03 +05:30
Shivam Mishra fe634293d8 chore: remove unused import 2024-11-27 17:06:45 +05:30
Shivam Mishra 4da133fe12 feat: add message meta component 2024-11-27 17:05:13 +05:30
Shivam Mishra 71e706a0bf feat: add in progress message 2024-11-27 16:46:46 +05:30
Shivam Mishra f8a400e704 feat: use content attributes 2024-11-27 16:33:53 +05:30
Shivam Mishra d70fa78e7d feat: show message status for outgoing messages 2024-11-27 16:27:13 +05:30
Shivam Mishra faf78bd447 feat: hide avatar for right oriented messages 2024-11-27 16:17:42 +05:30
Shivam Mishra b753d27301 feat: group when elements are in the same minute 2024-11-27 16:13:19 +05:30
Shivam Mishra caeb3bc88c feat: separate error component 2024-11-27 16:09:54 +05:30
Shivam Mishra 4dd7a5f693 feat: allow error 2024-11-27 16:07:27 +05:30
Shivam Mishra e3eca0dad8 feat: allow error variant 2024-11-27 15:56:29 +05:30
Shivam Mishra f2d115d2d7 fix: styling for p tag 2024-11-21 20:42:39 +05:30
Shivam MishraandGitHub 6ba5986b27 Merge branch 'develop' into feat/message-bubble 2024-11-21 20:37:29 +05:30
Shivam Mishra 2ed0d6a35d feat: don't use prose
We need to add the styling first
2024-11-21 20:35:02 +05:30
Shivam Mishra 4bb981566f fix: grouping space 2024-11-21 20:33:13 +05:30
Shivam Mishra fe4e906fc6 feat: format md 2024-11-21 20:31:42 +05:30
Shivam Mishra 036bfd830b feat: add grouping example 2024-11-21 20:05:45 +05:30
Shivam Mishra abfdd4d7fe chore: add some types 2024-11-21 19:49:35 +05:30
Shivam Mishra d2634cb9a3 feat: use realworld like data 2024-11-21 19:44:30 +05:30
Shivam Mishra c3327502a1 feat: add message container 2024-11-21 17:41:02 +05:30
Shivam Mishra 1fe1ace4a0 fix: color 2024-11-21 17:04:08 +05:30
Shivam Mishra 3a4e13922a feat: kickstart message bubble 2024-11-21 16:58:46 +05:30
Shivam Mishra b9cb39a9dd feat: allow dark across 2024-11-21 16:57:31 +05:30
17 changed files with 133 additions and 781 deletions
+3 -7
View File
@@ -12,7 +12,6 @@ on:
- master
tags:
- v*
# pull_request:
workflow_dispatch:
jobs:
@@ -41,9 +40,7 @@ jobs:
- name: set docker tag
run: |
# Replace forward slashes with hyphens in the ref name
SANITIZED_REF=$(echo "$GIT_REF" | sed 's/\//-/g')
echo "DOCKER_TAG=chatwoot/chatwoot:$SANITIZED_REF-ce" >> $GITHUB_ENV
echo "DOCKER_TAG=chatwoot/chatwoot:$GIT_REF-ce" >> $GITHUB_ENV
- name: replace docker tag if master
if: github.ref_name == 'master'
@@ -51,7 +48,6 @@ jobs:
echo "DOCKER_TAG=chatwoot/chatwoot:latest-ce" >> $GITHUB_ENV
- name: Login to DockerHub
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
@@ -62,6 +58,6 @@ jobs:
with:
context: .
file: docker/Dockerfile
platforms: linux/amd64, linux/arm64
push: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
platforms: linux/amd64
push: true
tags: ${{ env.DOCKER_TAG }}
@@ -18,10 +18,6 @@ const props = defineProps({
type: Boolean,
default: false,
},
conversationInboxType: {
type: String,
required: true,
},
});
const emit = defineEmits(['sendMessage']);
@@ -51,7 +47,7 @@ watch(
<template>
<div class="flex flex-col ]mx-auto h-full text-sm leading-6 tracking-tight">
<div ref="chatContainer" class="flex-1 px-4 py-4 space-y-6 overflow-y-auto">
<div ref="chatContainer" class="flex-1 overflow-y-auto py-4 space-y-6 px-4">
<template v-for="message in messages" :key="message.id">
<CopilotAgentMessage
v-if="message.role === 'user'"
@@ -61,13 +57,12 @@ watch(
<CopilotAssistantMessage
v-else-if="COPILOT_USER_ROLES.includes(message.role)"
:message="message"
:conversation-inbox-type="conversationInboxType"
/>
</template>
<CopilotLoader v-if="isCaptainTyping" />
</div>
<CopilotInput class="mx-3 mt-px mb-4" @send="sendMessage" />
<CopilotInput class="mx-3 mb-4 mt-px" @send="sendMessage" />
</div>
</template>
@@ -1,36 +1,12 @@
<script setup>
import { computed } from 'vue';
import { emitter } from 'shared/helpers/mitt';
import { BUS_EVENTS } from 'shared/constants/busEvents';
import { INBOX_TYPES } from 'dashboard/helper/inbox';
import Button from 'dashboard/components-next/button/Button.vue';
import Avatar from '../avatar/Avatar.vue';
const props = defineProps({
defineProps({
message: {
type: Object,
required: true,
},
conversationInboxType: {
type: String,
required: true,
},
});
const insertIntoRichEditor = computed(() => {
return [INBOX_TYPES.WEB, INBOX_TYPES.EMAIL].includes(
props.conversationInboxType
);
});
const useCopilotResponse = () => {
if (insertIntoRichEditor.value) {
emitter.emit(BUS_EVENTS.INSERT_INTO_RICH_EDITOR, props.message?.content);
} else {
emitter.emit(BUS_EVENTS.INSERT_INTO_NORMAL_EDITOR, props.message?.content);
}
};
</script>
<template>
@@ -46,15 +22,6 @@ const useCopilotResponse = () => {
<div class="break-words">
{{ message.content }}
</div>
<div class="flex flex-row mt-1">
<Button
:label="$t('CAPTAIN.COPILOT.USE')"
faded
sm
slate
@click="useCopilotResponse"
/>
</div>
</div>
</div>
</template>
@@ -1,12 +1,6 @@
<script setup>
import { computed, ref, defineAsyncComponent } from 'vue';
import { computed, defineAsyncComponent } from 'vue';
import { provideMessageContext } from './provider.js';
import { useTrack } from 'dashboard/composables';
import { emitter } from 'shared/helpers/mitt';
import { LocalStorage } from 'shared/helpers/localStorage';
import { ACCOUNT_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
import { BUS_EVENTS } from 'shared/constants/busEvents';
import {
MESSAGE_TYPES,
ATTACHMENT_TYPES,
@@ -14,7 +8,6 @@ import {
SENDER_TYPES,
ORIENTATION,
MESSAGE_STATUS,
CONTENT_TYPES,
} from './constants';
import Avatar from 'next/avatar/Avatar.vue';
@@ -37,7 +30,6 @@ const LocationBubble = defineAsyncComponent(
import MessageError from './MessageError.vue';
import MessageMeta from './MessageMeta.vue';
import ContextMenu from 'dashboard/modules/conversations/components/MessageContextMenu.vue';
/**
* @typedef {Object} Attachment
@@ -73,7 +65,7 @@ import ContextMenu from 'dashboard/modules/conversations/components/MessageConte
/**
* @typedef {Object} Props
* @property {('sent'|'delivered'|'read'|'failed'|'progress')} status - The delivery status of the message
* @property {('sent'|'delivered'|'read'|'failed')} status - The delivery status of the message
* @property {ContentAttributes} [contentAttributes={}] - Additional attributes of the message content
* @property {Attachment[]} [attachments=[]] - The attachments associated with the message
* @property {Sender|null} [sender=null] - The sender information
@@ -86,11 +78,6 @@ import ContextMenu from 'dashboard/modules/conversations/components/MessageConte
* @property {string|null} [error=null] - Error message if the message failed to send
* @property {string|null} [senderType=null] - The type of the sender
* @property {string} content - The message content
* @property {boolean} [groupWithNext=false] - Whether the message should be grouped with the next message
* @property {Object|null} [inReplyTo=null] - The message to which this message is a reply
* @property {boolean} [isEmailInbox=false] - Whether the message is from an email inbox
* @property {number} conversationId - The ID of the conversation to which the message belongs
* @property {number} inboxId - The ID of the inbox to which the message belongs
*/
// eslint-disable-next-line vue/define-macros-order
@@ -106,51 +93,70 @@ const props = defineProps({
required: true,
validator: value => Object.values(MESSAGE_STATUS).includes(value),
},
attachments: { type: Array, default: () => [] },
content: { type: String, default: null },
contentAttributes: { type: Object, default: () => ({}) },
contentType: {
attachments: {
type: Array,
default: () => [],
},
private: {
type: Boolean,
default: false,
},
createdAt: {
type: Number,
required: true,
},
sender: {
type: Object,
default: null,
},
senderId: {
type: Number,
default: null,
},
senderType: {
type: String,
default: 'text',
validator: value => Object.values(CONTENT_TYPES).includes(value),
default: null,
},
content: {
type: String,
required: true,
},
contentAttributes: {
type: Object,
default: () => {},
},
currentUserId: {
type: Number,
required: true,
},
groupWithNext: {
type: Boolean,
default: false,
},
inReplyTo: {
type: Object,
default: null,
},
isEmailInbox: {
type: Boolean,
default: false,
},
conversationId: { type: Number, required: true },
createdAt: { type: Number, required: true },
currentUserId: { type: Number, required: true },
groupWithNext: { type: Boolean, default: false },
inboxId: { type: Number, required: true },
inboxSupportsReplyTo: { type: Object, default: () => ({}) },
inReplyTo: { type: Object, default: null },
isEmailInbox: { type: Boolean, default: false },
private: { type: Boolean, default: false },
sender: { type: Object, default: null },
senderId: { type: Number, default: null },
senderType: { type: String, default: null },
sourceId: { type: String, default: '' },
});
const contextMenuPosition = ref({});
const showContextMenu = ref(false);
/**
* Computes the message variant based on props
* @type {import('vue').ComputedRef<'user'|'agent'|'activity'|'private'|'bot'|'template'>}
*/
const variant = computed(() => {
if (props.private) return MESSAGE_VARIANTS.PRIVATE;
if (props.isEmailInbox) {
const emailInboxTypes = [MESSAGE_TYPES.INCOMING, MESSAGE_TYPES.OUTGOING];
if (emailInboxTypes.includes(props.messageType)) {
return MESSAGE_VARIANTS.EMAIL;
}
}
if (props.contentType === CONTENT_TYPES.INCOMING_EMAIL) {
return MESSAGE_VARIANTS.EMAIL;
}
if (props.status === MESSAGE_STATUS.FAILED) return MESSAGE_VARIANTS.ERROR;
if (props.contentAttributes?.isUnsupported)
if (props.contentAttributes.isUnsupported)
return MESSAGE_VARIANTS.UNSUPPORTED;
const variants = {
@@ -164,20 +170,10 @@ const variant = computed(() => {
});
const isMyMessage = computed(() => {
// if an outgoing message is still processing, then it's definitely a
// message sent by the current user
if (
props.status === MESSAGE_STATUS.PROGRESS &&
props.messageType === MESSAGE_TYPES.OUTGOING
) {
return true;
}
const senderId = props.senderId ?? props.sender?.id;
const senderType = props.senderType ?? props.sender?.type;
if (!senderType || !senderId) {
return false;
}
if (!senderType || !senderId) return false;
return (
senderType.toLowerCase() === SENDER_TYPES.USER.toLowerCase() &&
@@ -252,11 +248,7 @@ const componentToRender = computed(() => {
if (emailInboxTypes.includes(props.messageType)) return EmailBubble;
}
if (props.contentType === CONTENT_TYPES.INCOMING_EMAIL) {
return EmailBubble;
}
if (props.contentAttributes?.isUnsupported) {
if (props.contentAttributes.isUnsupported) {
return UnsupportedBubble;
}
@@ -268,7 +260,7 @@ const componentToRender = computed(() => {
return InstagramStoryBubble;
}
if (Array.isArray(props.attachments) && props.attachments.length === 1) {
if (props.attachments.length === 1) {
const fileType = props.attachments[0].fileType;
if (!props.content) {
@@ -283,88 +275,13 @@ const componentToRender = computed(() => {
if (fileType === ATTACHMENT_TYPES.CONTACT) return ContactBubble;
}
if (
Array.isArray(props.attachments) &&
props.attachments.length > 1 &&
!props.content
) {
if (props.attachments.length > 1 && !props.content) {
return AttachmentsBubble;
}
return TextBubble;
});
const shouldShowContextMenu = computed(() => {
return !(
props.status === MESSAGE_STATUS.FAILED ||
props.status === MESSAGE_STATUS.PROGRESS ||
props.contentAttributes?.isUnsupported
);
});
const isBubble = computed(() => {
return props.messageType !== MESSAGE_TYPES.ACTIVITY;
});
const isMessageDeleted = computed(() => {
return props.contentAttributes?.deleted;
});
const payloadForContextMenu = computed(() => {
return {
id: props.id,
content_attributes: props.contentAttributes,
content: props.content,
conversation_id: props.conversationId,
};
});
const contextMenuEnabledOptions = computed(() => {
const hasText = !!props.content;
const hasAttachments = !!(props.attachments && props.attachments.length > 0);
const isOutgoing = props.messageType === MESSAGE_TYPES.OUTGOING;
return {
copy: hasText,
delete: hasText || hasAttachments,
cannedResponse: isOutgoing && hasText,
replyTo: !props.private && props.inboxSupportsReplyTo.outgoing,
};
});
function openContextMenu(e) {
const shouldSkipContextMenu =
e.target?.classList.contains('skip-context-menu') ||
e.target?.tagName.toLowerCase() === 'a';
if (shouldSkipContextMenu || getSelection().toString()) {
return;
}
e.preventDefault();
if (e.type === 'contextmenu') {
useTrack(ACCOUNT_EVENTS.OPEN_MESSAGE_CONTEXT_MENU);
}
contextMenuPosition.value = {
x: e.pageX || e.clientX,
y: e.pageY || e.clientY,
};
showContextMenu.value = true;
}
function closeContextMenu() {
showContextMenu.value = false;
contextMenuPosition.value = { x: null, y: null };
}
function handleReplyTo() {
const replyStorageKey = LOCAL_STORAGE_KEYS.MESSAGE_REPLY_TO;
const { conversationId, id: replyTo } = props;
LocalStorage.updateJsonStore(replyStorageKey, conversationId, replyTo);
emitter.emit(BUS_EVENTS.TOGGLE_REPLY_TO_MESSAGE, props);
}
provideMessageContext({
variant,
inReplyTo: props.inReplyTo,
@@ -375,7 +292,6 @@ provideMessageContext({
<template>
<div
:id="`message${props.id}`"
class="flex w-full"
:data-message-id="props.id"
:class="[flexOrientationClass, shouldGroupWithNext ? 'mb-2' : 'mb-4']"
@@ -408,11 +324,10 @@ provideMessageContext({
/>
</div>
<div
class="[grid-area:bubble] flex"
class="[grid-area:bubble]"
:class="{
'pl-9': ORIENTATION.RIGHT === orientation,
}"
@contextmenu="openContextMenu($event)"
>
<Component :is="componentToRender" v-bind="props" />
</div>
@@ -429,22 +344,8 @@ provideMessageContext({
:sender="props.sender"
:status="props.status"
:private="props.private"
:message-type="props.messageType"
:is-my-message="isMyMessage"
:created-at="props.createdAt"
:source-id="props.sourceId"
/>
</div>
<div v-if="shouldShowContextMenu" class="context-menu-wrap">
<ContextMenu
v-if="isBubble && !isMessageDeleted"
:context-menu-position="contextMenuPosition"
:is-open="showContextMenu"
:enabled-options="contextMenuEnabledOptions"
:message="payloadForContextMenu"
hide-button
@open="openContextMenu"
@close="closeContextMenu"
@reply-to="handleReplyTo"
/>
</div>
</div>
@@ -1,146 +0,0 @@
<script setup>
import { defineProps, computed } from 'vue';
import Message from './Message.vue';
import { useCamelCase } from 'dashboard/composables/useTransformKeys';
/**
* Props definition for the component
* @typedef {Object} Props
* @property {Array} readMessages - Array of read messages
* @property {Array} unReadMessages - Array of unread messages
* @property {Number} currentUserId - ID of the current user
* @property {Boolean} isAnEmailChannel - Whether this is an email channel
* @property {Object} inboxSupportsReplyTo - Inbox reply support configuration
* @property {Array} messages - Array of all messages
*/
const props = defineProps({
readMessages: {
type: Array,
default: () => [],
},
unReadMessages: {
type: Array,
default: () => [],
},
currentUserId: {
type: Number,
required: true,
},
isAnEmailChannel: {
type: Boolean,
default: false,
},
inboxSupportsReplyTo: {
type: Object,
default: () => ({ incoming: false, outgoing: false }),
},
messages: {
type: Array,
default: () => [],
},
shouldShowSpinner: {
type: Boolean,
default: false,
},
unreadMessageCount: {
type: Number,
default: 0,
},
});
const unread = computed(() => {
return useCamelCase(props.unReadMessages, { deep: true });
});
const read = computed(() => {
return useCamelCase(props.readMessages, { deep: true });
});
/**
* Determines if a message should be grouped with the next message
* @param {Number} index - Index of the current message
* @param {Array} messages - Array of messages to check
* @returns {Boolean} - Whether the message should be grouped with next
*/
const shouldGroupWithNext = (index, messages) => {
if (index === messages.length - 1) return false;
const current = messages[index];
const next = messages[index + 1];
if (next.status === 'failed') return false;
const nextSenderId = next.senderId ?? next.sender?.id;
const currentSenderId = current.senderId ?? current.sender?.id;
if (currentSenderId !== nextSenderId) return false;
// Check if messages are in the same minute by rounding down to nearest minute
return Math.floor(next.createdAt / 60) === Math.floor(current.createdAt / 60);
};
/**
* Gets the message that was replied to
* @param {Object} parentMessage - The message containing the reply reference
* @returns {Object|null} - The message being replied to, or null if not found
*/
const getInReplyToMessage = parentMessage => {
if (!parentMessage) return null;
const inReplyToMessageId =
parentMessage.contentAttributes?.inReplyTo ??
parentMessage.content_attributes?.in_reply_to;
if (!inReplyToMessageId) return null;
// Find in-reply-to message in the messages prop
const replyMessage = props.messages?.find(
message => message.id === inReplyToMessageId
);
return replyMessage ? useCamelCase(replyMessage) : null;
};
</script>
<template>
<ul class="px-4 bg-n-background">
<transition name="slide-up">
<!-- eslint-disable-next-line vue/require-toggle-inside-transition -->
<li class="min-h-[4rem]">
<span v-if="shouldShowSpinner" class="spinner message" />
</li>
</transition>
<Message
v-for="(message, index) in read"
:key="message.id"
v-bind="message"
:is-email-inbox="isAnEmailChannel"
:in-reply-to="getInReplyToMessage(message)"
:group-with-next="shouldGroupWithNext(index, readMessages)"
:inbox-supports-reply-to="inboxSupportsReplyTo"
:current-user-id="currentUserId"
data-clarity-mask="True"
/>
<li v-show="unreadMessageCount != 0" class="unread--toast">
<span>
{{ unreadMessageCount > 9 ? '9+' : unreadMessageCount }}
{{
unreadMessageCount > 1
? $t('CONVERSATION.UNREAD_MESSAGES')
: $t('CONVERSATION.UNREAD_MESSAGE')
}}
</span>
</li>
<Message
v-for="(message, index) in unread"
:key="message.id"
v-bind="message"
:in-reply-to="getInReplyToMessage(message)"
:group-with-next="shouldGroupWithNext(index, unReadMessages)"
:inbox-supports-reply-to="inboxSupportsReplyTo"
:current-user-id="currentUserId"
:is-email-inbox="isAnEmailChannel"
data-clarity-mask="True"
/>
<slot name="after" />
</ul>
</template>
@@ -4,9 +4,8 @@ import { messageTimestamp } from 'shared/helpers/timeHelper';
import MessageStatus from './MessageStatus.vue';
import Icon from 'next/icon/Icon.vue';
import { useInbox } from 'dashboard/composables/useInbox';
import { MESSAGE_STATUS, MESSAGE_TYPES } from './constants';
import { MESSAGE_STATUS } from './constants';
/**
* @typedef {Object} Sender
@@ -44,116 +43,21 @@ const props = defineProps({
type: Boolean,
default: false,
},
isMyMessage: {
type: Boolean,
default: false,
},
createdAt: {
type: Number,
required: true,
},
sourceId: {
type: String,
default: '',
},
messageType: {
type: Number,
required: true,
validator: value => Object.values(MESSAGE_TYPES).includes(value),
},
});
const {
isAFacebookInbox,
isALineChannel,
isAPIInbox,
isASmsInbox,
isATelegramChannel,
isATwilioChannel,
isAWebWidgetInbox,
isAWhatsAppChannel,
isAnEmailChannel,
} = useInbox();
const readableTime = computed(() =>
messageTimestamp(props.createdAt, 'LLL d, h:mm a')
);
const showSender = computed(() => !props.isMyMessage && props.sender);
const showStatusIndicator = computed(() => {
if (props.private) return false;
if (props.messageType === MESSAGE_TYPES.OUTGOING) return true;
if (props.messageType === MESSAGE_TYPES.TEMPLATE) return true;
return false;
});
const isSent = computed(() => {
if (!showStatusIndicator.value) return false;
// Messages will be marked as sent for the Email channel if they have a source ID.
if (isAnEmailChannel.value) return !!props.sourceId;
if (
isAWhatsAppChannel.value ||
isATwilioChannel.value ||
isAFacebookInbox.value ||
isASmsInbox.value ||
isATelegramChannel.value
) {
return props.sourceId && props.status === MESSAGE_STATUS.SENT;
}
// All messages will be mark as sent for the Line channel, as there is no source ID.
if (props.isALineChannel) return true;
return false;
});
const isDelivered = computed(() => {
if (!showStatusIndicator.value) return false;
if (
isAWhatsAppChannel.value ||
isATwilioChannel.value ||
isASmsInbox.value ||
isAFacebookInbox.value
) {
return props.sourceId && props.status === MESSAGE_STATUS.DELIVERED;
}
// All messages marked as delivered for the web widget inbox and API inbox once they are sent.
if (isAWebWidgetInbox.value || isAPIInbox.value) {
return props.status === MESSAGE_STATUS.SENT;
}
if (isALineChannel.value) {
return props.status === MESSAGE_STATUS.DELIVERED;
}
return false;
});
const isRead = computed(() => {
if (!showStatusIndicator.value) return false;
if (
isAWhatsAppChannel.value ||
isATwilioChannel.value ||
isAFacebookInbox.value
) {
return props.sourceId && props.status === MESSAGE_STATUS.READ;
}
if (isAWebWidgetInbox.value || isAPIInbox.value) {
return props.status === MESSAGE_STATUS.READ;
}
return false;
});
const statusToShow = computed(() => {
if (isRead.value) return MESSAGE_STATUS.READ;
if (isDelivered.value) return MESSAGE_STATUS.DELIVERED;
if (isSent.value) return MESSAGE_STATUS.SENT;
return MESSAGE_STATUS.PROGRESS;
});
</script>
<template>
@@ -168,6 +72,6 @@ const statusToShow = computed(() => {
icon="i-lucide-lock-keyhole"
class="text-n-slate-10 size-3"
/>
<MessageStatus v-if="showStatusIndicator" :status="statusToShow" />
<MessageStatus v-if="props.isMyMessage" :status />
</div>
</template>
@@ -2,7 +2,7 @@
import { computed, onMounted, nextTick, useTemplateRef } from 'vue';
import BaseAttachmentBubble from './BaseAttachment.vue';
import { useI18n } from 'vue-i18n';
// import maplibregl from 'maplibre-gl';
import maplibregl from 'maplibre-gl';
/**
* @typedef {Object} Attachment
@@ -57,28 +57,28 @@ const mapUrl = computed(
const mapContainer = useTemplateRef('mapContainer');
// const setupMap = () => {
// const map = new maplibregl.Map({
// style: 'https://tiles.openfreemap.org/styles/positron',
// center: [long.value, lat.value],
// zoom: 15,
// container: mapContainer.value,
// attributionControl: false,
// dragPan: false,
// dragRotate: false,
// scrollZoom: false,
// touchZoom: false,
// touchRotate: false,
// keyboard: false,
// doubleClickZoom: false,
// });
// new maplibregl.Marker().setLngLat([long.value, lat.value]).addTo(map);
// return map;
// };
const setupMap = () => {
const map = new maplibregl.Map({
style: 'https://tiles.openfreemap.org/styles/positron',
center: [long.value, lat.value],
zoom: 9.5,
container: mapContainer.value,
attributionControl: false,
dragPan: false,
dragRotate: false,
scrollZoom: false,
touchZoom: false,
touchRotate: false,
keyboard: false,
doubleClickZoom: false,
});
return map;
};
onMounted(async () => {
await nextTick();
// setupMap();
setupMap();
});
</script>
@@ -8,10 +8,6 @@ const props = defineProps({
type: [Number, String],
required: true,
},
conversationInboxType: {
type: String,
required: true,
},
});
const currentUser = useMapGetter('getCurrentUser');
const messages = ref([]);
@@ -57,7 +53,6 @@ const sendMessage = async message => {
:messages="messages"
:support-agent="currentUser"
:is-captain-typing="isCaptainTyping"
:conversation-inbox-type="conversationInboxType"
@send-message="sendMessage"
/>
</template>
@@ -6,7 +6,7 @@ import ContactPanel from 'dashboard/routes/dashboard/conversation/ContactPanel.v
import TabBar from 'dashboard/components-next/tabbar/TabBar.vue';
import { useI18n } from 'vue-i18n';
const props = defineProps({
defineProps({
currentChat: {
required: true,
type: Object,
@@ -16,13 +16,11 @@ const props = defineProps({
const emit = defineEmits(['toggleContactPanel']);
const getters = useStoreGetters();
const { t } = useI18n();
const captainIntegration = computed(() =>
getters['integrations/getIntegration'].value('captain', null)
);
const channelType = computed(() => props.currentChat?.meta?.channel || '');
const { t } = useI18n();
const CONTACT_TABS_OPTIONS = [
{ key: 'CONTACT', value: 'contact' },
@@ -63,7 +61,7 @@ const showCopilotTab = computed(() => {
@tab-changed="handleTabChange"
/>
</div>
<div class="flex flex-1 overflow-auto">
<div class="overflow-auto flex flex-1">
<ContactPanel
v-if="!activeTab"
:conversation-id="currentChat.id"
@@ -73,7 +71,6 @@ const showCopilotTab = computed(() => {
<CopilotContainer
v-else-if="activeTab === 1 && showCopilotTab"
:key="currentChat.id"
:conversation-inbox-type="channelType"
:conversation-id="currentChat.id"
class="flex-1"
/>
@@ -4,13 +4,10 @@ import { ref } from 'vue';
import { useConfig } from 'dashboard/composables/useConfig';
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
import { useAI } from 'dashboard/composables/useAI';
import { useAccount } from 'dashboard/composables/useAccount';
import { useCamelCase } from 'dashboard/composables/useTransformKeys';
// components
import ReplyBox from './ReplyBox.vue';
import Message from './Message.vue';
import NextMessage from 'next/message/Message.vue';
import ConversationLabelSuggestion from './conversation/LabelSuggestion.vue';
import Banner from 'dashboard/components/ui/Banner.vue';
@@ -37,26 +34,9 @@ import { REPLY_POLICY } from 'shared/constants/links';
import wootConstants from 'dashboard/constants/globals';
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
function shouldGroupWithNext(index, messages) {
if (index === messages.length - 1) return false;
const current = messages[index];
const next = messages[index + 1];
if (next.status === 'failed') return false;
const nextSenderId = next.senderId ?? next.sender?.id;
const currentSenderId = current.senderId ?? current.sender?.id;
if (currentSenderId !== nextSenderId) return false;
// Check if messages are in the same minute by rounding down to nearest minute
return Math.floor(next.createdAt / 60) === Math.floor(current.createdAt / 60);
}
export default {
components: {
Message,
NextMessage,
ReplyBox,
Banner,
ConversationLabelSuggestion,
@@ -76,7 +56,6 @@ export default {
setup() {
const isPopOutReplyBox = ref(false);
const { isEnterprise } = useConfig();
const { accountId } = useAccount();
const closePopOutReplyBox = () => {
isPopOutReplyBox.value = false;
@@ -101,10 +80,6 @@ export default {
fetchLabelSuggestions,
} = useAI();
const showNextBubbles = LocalStorage.get(
LOCAL_STORAGE_KEYS.USE_NEXT_BUBBLE
);
return {
isEnterprise,
isPopOutReplyBox,
@@ -114,8 +89,6 @@ export default {
isLabelSuggestionFeatureEnabled,
fetchIntegrationsIfRequired,
fetchLabelSuggestions,
accountId,
showNextBubbles,
};
},
data() {
@@ -133,7 +106,6 @@ export default {
computed: {
...mapGetters({
currentChat: 'getSelectedChat',
currentUserId: 'getCurrentUserID',
listLoadingStatus: 'getAllMessagesLoaded',
currentAccountId: 'getCurrentAccountId',
}),
@@ -181,28 +153,16 @@ export default {
return messages;
},
readMessages() {
const readMessages = getReadMessages(
return getReadMessages(
this.getMessages,
this.currentChat.agent_last_seen_at
);
if (this.showNextBubbles) {
return useCamelCase(readMessages, { deep: true });
}
return readMessages;
},
unReadMessages() {
const unreadMessages = getUnreadMessages(
return getUnreadMessages(
this.getMessages,
this.currentChat.agent_last_seen_at
);
if (this.showNextBubbles) {
return useCamelCase(unreadMessages, { deep: true });
}
return unreadMessages;
},
shouldShowSpinner() {
return (
@@ -476,19 +436,11 @@ export default {
makeMessagesRead() {
this.$store.dispatch('markMessagesRead', { id: this.currentChat.id });
},
getInReplyToMessage(parentMessage) {
// the old implementation took an empty object, but the
// new implementation takes null
const emptyOption = this.showNextBubbles ? null : {};
if (!parentMessage) return emptyOption;
// to maintain backward compatibility we use both the keys
// contentAttributes and content_attributes
// TODO: Remove this once we've migrated all the keys to camelCase
const inReplyToMessageId =
parentMessage.contentAttributes?.inReplyTo ??
parentMessage.content_attributes?.in_reply_to;
if (!inReplyToMessageId) return emptyOption;
getInReplyToMessage(parentMessage) {
if (!parentMessage) return {};
const inReplyToMessageId = parentMessage.content_attributes?.in_reply_to;
if (!inReplyToMessageId) return {};
return this.currentChat?.messages.find(message => {
if (message.id === inReplyToMessageId) {
@@ -497,7 +449,6 @@ export default {
return false;
});
},
shouldGroupWithNext: shouldGroupWithNext,
},
};
</script>
@@ -522,46 +473,28 @@ export default {
@click="onToggleContactPanel"
/>
</div>
<ul
class="conversation-panel"
:class="{ 'px-4 bg-n-background': showNextBubbles }"
>
<ul class="conversation-panel">
<transition name="slide-up">
<!-- eslint-disable-next-line vue/require-toggle-inside-transition -->
<li class="min-h-[4rem]">
<span v-if="shouldShowSpinner" class="spinner message" />
</li>
</transition>
<template v-if="showNextBubbles">
<NextMessage
v-for="(message, index) in readMessages"
:key="message.id"
v-bind="message"
:is-email-inbox="isAnEmailChannel"
:in-reply-to="getInReplyToMessage(message)"
:group-with-next="shouldGroupWithNext(index, readMessages)"
:inbox-supports-reply-to="inboxSupportsReplyTo"
:current-user-id="currentUserId"
data-clarity-mask="True"
/>
</template>
<template v-else>
<Message
v-for="message in readMessages"
:key="message.id"
class="message--read ph-no-capture"
data-clarity-mask="True"
:data="message"
:is-a-tweet="isATweet"
:is-a-whatsapp-channel="isAWhatsAppChannel"
:is-web-widget-inbox="isAWebWidgetInbox"
:is-a-facebook-inbox="isAFacebookInbox"
:is-an-email-inbox="isAnEmailChannel"
:is-instagram="isInstagramDM"
:inbox-supports-reply-to="inboxSupportsReplyTo"
:in-reply-to="getInReplyToMessage(message)"
/>
</template>
<Message
v-for="message in readMessages"
:key="message.id"
class="message--read ph-no-capture"
data-clarity-mask="True"
:data="message"
:is-a-tweet="isATweet"
:is-a-whatsapp-channel="isAWhatsAppChannel"
:is-web-widget-inbox="isAWebWidgetInbox"
:is-a-facebook-inbox="isAFacebookInbox"
:is-an-email-inbox="isAnEmailChannel"
:is-instagram="isInstagramDM"
:inbox-supports-reply-to="inboxSupportsReplyTo"
:in-reply-to="getInReplyToMessage(message)"
/>
<li v-show="unreadMessageCount != 0" class="unread--toast">
<span>
{{ unreadMessageCount > 9 ? '9+' : unreadMessageCount }}
@@ -572,35 +505,20 @@ export default {
}}
</span>
</li>
<template v-if="showNextBubbles">
<NextMessage
v-for="(message, index) in unReadMessages"
:key="message.id"
v-bind="message"
:in-reply-to="getInReplyToMessage(message)"
:group-with-next="shouldGroupWithNext(index, unReadMessages)"
:inbox-supports-reply-to="inboxSupportsReplyTo"
:current-user-id="currentUserId"
:is-email-inbox="isAnEmailChannel"
data-clarity-mask="True"
/>
</template>
<template v-else>
<Message
v-for="message in unReadMessages"
:key="message.id"
class="message--unread ph-no-capture"
data-clarity-mask="True"
:data="message"
:is-a-tweet="isATweet"
:is-a-whatsapp-channel="isAWhatsAppChannel"
:is-web-widget-inbox="isAWebWidgetInbox"
:is-a-facebook-inbox="isAFacebookInbox"
:is-instagram-dm="isInstagramDM"
:inbox-supports-reply-to="inboxSupportsReplyTo"
:in-reply-to="getInReplyToMessage(message)"
/>
</template>
<Message
v-for="message in unReadMessages"
:key="message.id"
class="message--unread ph-no-capture"
data-clarity-mask="True"
:data="message"
:is-a-tweet="isATweet"
:is-a-whatsapp-channel="isAWhatsAppChannel"
:is-web-widget-inbox="isAWebWidgetInbox"
:is-a-facebook-inbox="isAFacebookInbox"
:is-instagram-dm="isInstagramDM"
:inbox-supports-reply-to="inboxSupportsReplyTo"
:in-reply-to="getInReplyToMessage(message)"
/>
<ConversationLabelSuggestion
v-if="shouldShowLabelSuggestions"
:suggested-labels="labelSuggestions"
@@ -459,13 +459,11 @@ export default {
BUS_EVENTS.NEW_CONVERSATION_MODAL,
this.onNewConversationModalActive
);
emitter.on(BUS_EVENTS.INSERT_INTO_NORMAL_EDITOR, this.addIntoEditor);
},
unmounted() {
document.removeEventListener('paste', this.onPaste);
document.removeEventListener('keydown', this.handleKeyEvents);
emitter.off(BUS_EVENTS.TOGGLE_REPLY_TO_MESSAGE, this.fetchAndSetReplyTo);
emitter.off(BUS_EVENTS.INSERT_INTO_NORMAL_EDITOR, this.addIntoEditor);
emitter.off(
BUS_EVENTS.NEW_CONVERSATION_MODAL,
this.onNewConversationModalActive
@@ -1,141 +0,0 @@
import { computed } from 'vue';
import { useMapGetter } from 'dashboard/composables/store';
import { useCamelCase } from 'dashboard/composables/useTransformKeys';
import { INBOX_TYPES } from 'dashboard/helper/inbox';
export const INBOX_FEATURES = {
REPLY_TO: 'replyTo',
REPLY_TO_OUTGOING: 'replyToOutgoing',
};
// This is a single source of truth for inbox features
// This is used to check if a feature is available for a particular inbox or not
export const INBOX_FEATURE_MAP = {
[INBOX_FEATURES.REPLY_TO]: [
INBOX_TYPES.FB,
INBOX_TYPES.WEB,
INBOX_TYPES.TWITTER,
INBOX_TYPES.WHATSAPP,
INBOX_TYPES.TELEGRAM,
INBOX_TYPES.API,
],
[INBOX_FEATURES.REPLY_TO_OUTGOING]: [
INBOX_TYPES.WEB,
INBOX_TYPES.TWITTER,
INBOX_TYPES.WHATSAPP,
INBOX_TYPES.TELEGRAM,
INBOX_TYPES.API,
],
};
/**
* Composable for handling macro-related functionality
* @returns {Object} An object containing the getMacroDropdownValues function
*/
export const useInbox = () => {
const currentChat = useMapGetter('getSelectedChat');
const inboxGetter = useMapGetter('inboxes/getInboxById');
const inbox = computed(() => {
const inboxId = currentChat.value.inbox_id;
return useCamelCase(inboxGetter.value(inboxId), { deep: true });
});
const channelType = computed(() => {
return inbox.value.channelType;
});
const isAPIInbox = computed(() => {
return channelType.value === INBOX_TYPES.API;
});
const isAFacebookInbox = computed(() => {
return channelType.value === INBOX_TYPES.FB;
});
const isAWebWidgetInbox = computed(() => {
return channelType.value === INBOX_TYPES.WEB;
});
const isATwilioChannel = computed(() => {
return channelType.value === INBOX_TYPES.TWILIO;
});
const isALineChannel = computed(() => {
return channelType.value === INBOX_TYPES.LINE;
});
const isAnEmailChannel = computed(() => {
return channelType.value === INBOX_TYPES.EMAIL;
});
const isATelegramChannel = computed(() => {
return channelType.value === INBOX_TYPES.TELEGRAM;
});
const whatsAppAPIProvider = computed(() => {
return inbox.value.provider || '';
});
const isAMicrosoftInbox = computed(() => {
return isAnEmailChannel.value && inbox.value.provider === 'microsoft';
});
const isAGoogleInbox = computed(() => {
return isAnEmailChannel.value && inbox.value.provider === 'google';
});
const isATwilioSMSChannel = computed(() => {
const { medium: medium = '' } = inbox.value;
return isATwilioChannel.value && medium === 'sms';
});
const isASmsInbox = computed(() => {
return channelType.value === INBOX_TYPES.SMS || isATwilioSMSChannel.value;
});
const isATwilioWhatsAppChannel = computed(() => {
const { medium: medium = '' } = inbox.value;
return isATwilioChannel.value && medium === 'whatsapp';
});
const isAWhatsAppCloudChannel = computed(() => {
return (
channelType.value === INBOX_TYPES.WHATSAPP &&
whatsAppAPIProvider.value === 'whatsapp_cloud'
);
});
const is360DialogWhatsAppChannel = computed(() => {
return (
channelType.value === INBOX_TYPES.WHATSAPP &&
whatsAppAPIProvider.value === 'default'
);
});
const isAWhatsAppChannel = computed(() => {
return (
channelType.value === INBOX_TYPES.WHATSAPP ||
isATwilioWhatsAppChannel.value
);
});
return {
inbox,
isAFacebookInbox,
isALineChannel,
isAPIInbox,
isASmsInbox,
isATelegramChannel,
isATwilioChannel,
isAWebWidgetInbox,
isAWhatsAppChannel,
isAMicrosoftInbox,
isAGoogleInbox,
isATwilioWhatsAppChannel,
isAWhatsAppCloudChannel,
is360DialogWhatsAppChannel,
isAnEmailChannel,
};
};
@@ -3,7 +3,6 @@
import { unref } from 'vue';
import camelcaseKeys from 'camelcase-keys';
import snakecaseKeys from 'snakecase-keys';
import * as Sentry from '@sentry/vue';
/**
* Vue composable that converts object keys to camelCase
@@ -13,18 +12,8 @@ import * as Sentry from '@sentry/vue';
* @returns {Object|Array} Converted payload with camelCase keys
*/
export function useCamelCase(payload, options) {
try {
const unrefPayload = unref(payload);
return camelcaseKeys(unrefPayload, options);
} catch (e) {
Sentry.setContext('transform-keys-error', {
payload,
options,
op: 'camelCase',
});
Sentry.captureException(e);
return payload;
}
const unrefPayload = unref(payload);
return camelcaseKeys(unrefPayload, options);
}
/**
@@ -35,16 +24,6 @@ export function useCamelCase(payload, options) {
* @returns {Object|Array} Converted payload with snake_case keys
*/
export function useSnakeCase(payload, options) {
try {
const unrefPayload = unref(payload);
return snakecaseKeys(unrefPayload, options);
} catch (e) {
Sentry.setContext('transform-keys-error', {
payload,
options,
op: 'snakeCase',
});
Sentry.captureException(e);
return payload;
}
const unrefPayload = unref(payload);
return snakecaseKeys(unrefPayload, options);
}
@@ -5,5 +5,4 @@ export const LOCAL_STORAGE_KEYS = {
COLOR_SCHEME: 'color_scheme',
DISMISSED_LABEL_SUGGESTIONS: 'labelSuggestionsDismissed',
MESSAGE_REPLY_TO: 'messageReplyTo',
USE_NEXT_BUBBLE: 'useNextBubble',
};
@@ -305,8 +305,7 @@
"COPILOT": {
"SEND_MESSAGE": "Send message...",
"LOADER": "Captain is thinking",
"YOU": "You",
"USE": "Use this"
"YOU": "You"
}
}
}
@@ -4,7 +4,6 @@ import { mapGetters } from 'vuex';
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
import ContextMenu from 'dashboard/components/ui/ContextMenu.vue';
import AddCannedModal from 'dashboard/routes/dashboard/settings/canned/AddCanned.vue';
import { useSnakeCase } from 'dashboard/composables/useTransformKeys';
import { copyTextToClipboard } from 'shared/helpers/clipboard';
import { conversationUrl, frontendURL } from '../../../helper/URLHelper';
import {
@@ -39,10 +38,6 @@ export default {
type: Object,
default: () => ({}),
},
hideButton: {
type: Boolean,
default: false,
},
},
emits: ['open', 'close', 'replyTo'],
setup() {
@@ -67,7 +62,7 @@ export default {
return this.getPlainText(this.messageContent);
},
conversationId() {
return this.message.conversation_id ?? this.message.conversationId;
return this.message.conversation_id;
},
messageId() {
return this.message.id;
@@ -76,9 +71,7 @@ export default {
return this.message.content;
},
contentAttributes() {
return useSnakeCase(
this.message.content_attributes ?? this.message.contentAttributes
);
return this.message.content_attributes;
},
},
methods: {
@@ -190,7 +183,6 @@ export default {
:reject-text="$t('CONVERSATION.CONTEXT_MENU.DELETE_CONFIRMATION.CANCEL')"
/>
<woot-button
v-if="!hideButton"
icon="more-vertical"
color-scheme="secondary"
variant="clear"
@@ -14,5 +14,4 @@ export const BUS_EVENTS = {
SHOW_TOAST: 'newToastMessage',
NEW_CONVERSATION_MODAL: 'newConversationModal',
INSERT_INTO_RICH_EDITOR: 'insertIntoRichEditor',
INSERT_INTO_NORMAL_EDITOR: 'insertIntoNormalEditor',
};