@bcc-code/vue-bcc-chat-ui 3.12.0 → 3.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@bcc-code/vue-bcc-chat-ui",
3
3
  "author": "bcc-code",
4
4
  "license": "Apache-2.0",
5
- "version": "3.12.0",
5
+ "version": "3.14.0",
6
6
  "type": "module",
7
7
  "private": false,
8
8
  "files": [
@@ -0,0 +1,132 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ CometChatMessageEvents,
4
+ CometChatTheme,
5
+ MessageStatus,
6
+ } from "@cometchat/uikit-resources";
7
+ import { CloseIcon } from "@cometchat/chat-uikit-vue";
8
+ import {
9
+ getCurrentInstance,
10
+ ref,
11
+ inject,
12
+ Ref,
13
+ onBeforeUnmount,
14
+ onMounted,
15
+ } from "vue";
16
+ import { ChatInstance } from "../chat/types";
17
+ import BccAttachmentPreview from "./BccAttachmentPreview.vue";
18
+
19
+ const props = defineProps({
20
+ captionedAttachment: { type: Object },
21
+ closeButtonIconURL: { type: String, default: CloseIcon },
22
+ previewStyle: { type: Object, default: {} },
23
+ });
24
+
25
+ const chatInstance = inject<Ref<ChatInstance>>("chatInstance");
26
+ const vueInstance: any = getCurrentInstance();
27
+ const msgComposerData = vueInstance?.parent?.type?.name === "CometChatMessageComposer" && vueInstance?.parent?.setupState;
28
+
29
+ const { theme } = inject("theme", { theme: ref(new CometChatTheme({})) });
30
+
31
+ function closePreview(_event?: MouseEvent) {
32
+ if (chatInstance) {
33
+ chatInstance.value.captionedAttachment = null;
34
+ }
35
+ }
36
+
37
+ const onMessageEditStart = CometChatMessageEvents.ccMessageEdited.subscribe(
38
+ ({ status }) => {
39
+ if (status === MessageStatus.inprogress) {
40
+ closePreview();
41
+ }
42
+ }
43
+ );
44
+
45
+ const onMessageSend = CometChatMessageEvents.ccMessageSent.subscribe(
46
+ ({ status }) => {
47
+ if (status === MessageStatus.inprogress) {
48
+ closePreview();
49
+ }
50
+ }
51
+ );
52
+
53
+ onMounted(() => {
54
+ if (msgComposerData) {
55
+ msgComposerData.inputRef.querySelector(".messageinput-input").focus()
56
+ if (msgComposerData.messageToBeEdited) {
57
+ msgComposerData.messageToBeEdited = null;
58
+ msgComposerData.messageText = "";
59
+ msgComposerData.textRef = "";
60
+ msgComposerData.inputRef?.emptyInputField();
61
+ }
62
+ }
63
+ });
64
+
65
+ onBeforeUnmount(() => {
66
+ onMessageEditStart.unsubscribe();
67
+ onMessageSend.unsubscribe();
68
+ });
69
+
70
+ const closeBtnIconStyle = () => {
71
+ return {
72
+ buttonIconTint: theme.value.palette.getAccent500(),
73
+ height: "14px",
74
+ width: "14px",
75
+ border: "none",
76
+ borderRadius: "0",
77
+ background: "transparent",
78
+ display: "flex",
79
+ justifyContent: "center",
80
+ alignItems: "center",
81
+ };
82
+ };
83
+
84
+ const wrapperStyle = () => {
85
+ return {
86
+ background: theme.value.palette.getBackground(),
87
+ border: `1px solid ${theme.value.palette.getAccent200()}`,
88
+ height: "100%",
89
+ width: "100%",
90
+ borderRadius: "12px",
91
+ };
92
+ };
93
+ </script>
94
+
95
+ <template>
96
+ <div class="bcc__attachment_box" :style="wrapperStyle()">
97
+ <BccAttachmentPreview
98
+ :captionedAttachment="props.captionedAttachment"
99
+ :previewStyle="previewStyle"
100
+ />
101
+ <div class="bcc__attachment_preview__close" @click="closePreview">
102
+ <cometchat-button
103
+ :buttonStyle="closeBtnIconStyle()"
104
+ :iconURL="props.closeButtonIconURL"
105
+ ></cometchat-button>
106
+ </div>
107
+ </div>
108
+ </template>
109
+
110
+ <style>
111
+ .cc-messagecomposer-wrapper__headerview {
112
+ position: static !important;
113
+ }
114
+ </style>
115
+ <style scoped>
116
+ * {
117
+ box-sizing: border-box;
118
+ }
119
+ .bcc__attachment_box {
120
+ padding: 8px;
121
+ display: flex;
122
+ flex-direction: row;
123
+ justify-content: space-between;
124
+ margin: 0;
125
+ min-height: 30px;
126
+ }
127
+ .bcc__attachment_preview__close {
128
+ width: 14px;
129
+ height: 14px;
130
+ display: inline-block;
131
+ }
132
+ </style>
@@ -0,0 +1,37 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps({
3
+ captionedAttachment: { type: Object, default: {} },
4
+ previewStyle: { type: Object, default: {} },
5
+ });
6
+ </script>
7
+
8
+ <template>
9
+ <div class="bcc__attachment_preview__content_wrapper">
10
+ <div class="bcc__attachment_preview__content">
11
+ <img :src="props.captionedAttachment?.image" />
12
+ </div>
13
+ </div>
14
+ </template>
15
+
16
+ <style scoped>
17
+ .bcc__attachment_preview__content_wrapper {
18
+ padding: 0 5px 0 20px;
19
+ position: relative;
20
+ width: 100%;
21
+ }
22
+
23
+ .bcc__attachment_preview__content {
24
+ width: 100%;
25
+ max-height: 350px;
26
+ height: 100%;
27
+ display: flex;
28
+ justify-content: center;
29
+ align-items: center;
30
+ }
31
+
32
+ .bcc__attachment_preview__content>img {
33
+ width: 100%;
34
+ height: 100%;
35
+ object-fit: contain;
36
+ }
37
+ </style>
@@ -1,16 +1,36 @@
1
1
  <script setup lang="ts">
2
2
  import MarkdownIt from 'markdown-it'
3
- import { BaseMessage, CometChat, User } from '@cometchat/chat-sdk-javascript'
3
+ import { BaseMessage, CometChat, MediaMessage, User } from '@cometchat/chat-sdk-javascript'
4
4
  import DOMPurify from 'dompurify';
5
5
  import { inject, ref } from 'vue';
6
6
  import { CometChatTheme, fontHelper } from '@cometchat/uikit-resources';
7
7
  import BccReplyPreview from "./BccReplyPreview.vue";
8
-
9
- const props = defineProps<{
10
- text: string,
11
- metadata: Record<string, any>,
12
- mentionedUsers: User[],
13
- }>()
8
+ import BccImageBubble from './BccImageBubble.vue';
9
+
10
+ const props = defineProps({
11
+ text: {
12
+ type: String,
13
+ required: true
14
+ },
15
+ metadata: {
16
+ type: Object
17
+ },
18
+ mentionedUsers: {
19
+ type: Array<User>
20
+ },
21
+ message: {
22
+ type: MediaMessage,
23
+ default: null
24
+ },
25
+ placeholderImage: {
26
+ type: String,
27
+ default: null
28
+ },
29
+ src: {
30
+ type: String,
31
+ default: null
32
+ }
33
+ })
14
34
 
15
35
  function toMarkdownHtml(str: string) {
16
36
  str = formatMentionsInText(str);
@@ -73,7 +93,7 @@ const replyPreviewStyle = {
73
93
  </script>
74
94
 
75
95
  <template>
76
- <div>
96
+ <div :style="{'display': src != null ? 'table-caption' : 'block'}">
77
97
  <div v-if="metadata?.replyToMessageId" class="bcc-chat-msg-bubble">
78
98
  <div class="bcc-chat-msg-bubble--reply">
79
99
  <BccReplyPreview
@@ -82,6 +102,13 @@ const replyPreviewStyle = {
82
102
  />
83
103
  </div>
84
104
  </div>
105
+ <div v-if="src != null" style="display: block; width: max-content;">
106
+ <BccImageBubble
107
+ :message="message"
108
+ :placeholderImage="placeholderImage"
109
+ :src="src"
110
+ />
111
+ </div>
85
112
  <div v-if="metadata && metadata.translated_message" v-html="toMarkdownHtml(metadata.translated_message)"
86
113
  class="bcc-chat-msg-bubble bcc-chat-msg-bubble--translation" />
87
114
  <div v-html="toMarkdownHtml(text)" class="bcc-chat-msg-bubble"
@@ -23,10 +23,12 @@ import {
23
23
  } from "../chat/uiKit";
24
24
  import { ChatInstance } from "../chat/types";
25
25
  import { CometChatMessageEvents, MessageStatus } from "@cometchat/uikit-resources";
26
+ import BccAttachmentBox from "./BccAttachmentBox.vue";
27
+ import BccChatSendButton from "./BccChatSendButton.vue";
26
28
 
27
29
  const props = defineProps({
28
30
  chatUid: { type: String, required: true },
29
- senderDisplayName: { type: String, required: false}
31
+ senderDisplayName: { type: String, required: false }
30
32
  });
31
33
 
32
34
  const componentId = ref(
@@ -36,10 +38,11 @@ const chatGroup = ref<Group>();
36
38
 
37
39
  const chatInstance: Ref<ChatInstance> = ref({
38
40
  componentId: componentId,
39
- replyToMessage: null
41
+ replyToMessage: null,
42
+ captionedAttachment: null
40
43
  });
41
44
 
42
- const messageComposerConfiguration = getMessageComposerConfiguration();
45
+ const messageComposerConfiguration = getMessageComposerConfiguration(chatInstance);
43
46
  const messageListConfiguration = getMessageListConfiguration(chatInstance, chatGroup);
44
47
  const threadedMessagesConfiguration = getThreadedMessagesConfiguration(chatGroup);
45
48
 
@@ -47,8 +50,8 @@ provide("chatInstance", chatInstance);
47
50
 
48
51
  const onMessageSend = CometChatMessageEvents.ccMessageSent.subscribe(({ message, status }) => {
49
52
  if (status === MessageStatus.inprogress) {
50
- let metadata: any = {};
51
- let scope: string | undefined = chatGroup.value?.getScope()
53
+ let metadata: any = (message as any)?.getMetadata() || {};
54
+ let scope: string | undefined = chatGroup.value?.getScope();
52
55
 
53
56
  if (chatInstance.value.replyToMessage) {
54
57
  metadata.replyToMessageId = chatInstance.value.replyToMessage?.getId()
@@ -58,11 +61,7 @@ const onMessageSend = CometChatMessageEvents.ccMessageSent.subscribe(({ message,
58
61
  metadata.senderDisplayName = props.senderDisplayName
59
62
  }
60
63
 
61
- if (Object.keys(metadata).length === 0) {
62
- return
63
- }
64
-
65
- (message as any).setMetadata(metadata);
64
+ (message as any)?.setMetadata(metadata);
66
65
  }
67
66
  });
68
67
 
@@ -79,6 +78,26 @@ watchEffect(() => {
79
78
  }
80
79
  });
81
80
 
81
+ watchEffect(() => {
82
+ if (!chatInstance.value.captionedAttachment) {
83
+ messageComposerConfiguration.value.headerView = undefined;
84
+ messageComposerConfiguration.value.sendButtonView = undefined;
85
+ } else {
86
+ chatInstance.value.captionedAttachment.caption = messageComposerConfiguration.value.text;
87
+
88
+ messageComposerConfiguration.value.headerView = {
89
+ componentName: BccAttachmentBox,
90
+ props: {
91
+ captionedAttachment: chatInstance.value.captionedAttachment
92
+ }
93
+ }
94
+
95
+ messageComposerConfiguration.value.sendButtonView = {
96
+ componentName: BccChatSendButton
97
+ };
98
+ }
99
+ });
100
+
82
101
  watch([loggedIn, () => props.chatUid, chat.initialized], async ([_loggedIn, _chatUid, _initialized]) => {
83
102
  if (_loggedIn && _chatUid && _initialized) {
84
103
  chatGroup.value = (await chat.getGroup(props.chatUid)) ?? undefined;
@@ -111,6 +130,13 @@ onMounted(() => {
111
130
  shadowDomSelector: "cometchat-preview",
112
131
  }
113
132
  ]);
133
+
134
+ messageComposerConfiguration.value.onTextChange = (text: any) => {
135
+ messageComposerConfiguration.value.text = text;
136
+ if (chatInstance.value.captionedAttachment?.caption) {
137
+ chatInstance.value.captionedAttachment.caption = text;
138
+ }
139
+ }
114
140
  });
115
141
 
116
142
  onBeforeUnmount(() => {
@@ -122,7 +148,7 @@ onUnmounted(async () => {
122
148
  });
123
149
 
124
150
  function isChannel(): boolean {
125
- return chatGroup.value?.getMetadata()?.isChannel;
151
+ return (chatGroup.value?.getMetadata() as any)?.isChannel;
126
152
  }
127
153
 
128
154
  function isParticipant(): boolean {
@@ -249,6 +275,7 @@ accent900: text in avatar
249
275
 
250
276
  /* 1.1.3 Wrapper for Messages Composer Header View */
251
277
  .cc-messagecomposer-wrapper__headerview {
278
+ max-height: 100%;
252
279
  margin-bottom: 5px;
253
280
  }
254
281
  }
@@ -0,0 +1,90 @@
1
+ <script setup lang="ts">
2
+ import { localize } from "@cometchat/uikit-resources";
3
+ import { SendIcon } from "@cometchat/chat-uikit-vue";
4
+ import { sendMediaMessage } from "../chat/captionedAttachment";
5
+ import { ChatInstance } from "../chat/types";
6
+ import { getCurrentInstance, inject, onBeforeUnmount, onMounted, Ref } from "vue";
7
+
8
+ const sendButtonIconURL = SendIcon;
9
+ const sendButtonStyle = {
10
+ height: "24px",
11
+ width: "24px",
12
+ border: "none",
13
+ borderRadius: "0",
14
+ buttonIconTint: "#2fb5e9d1",
15
+ background: "transparent"
16
+ }
17
+
18
+ const chatInstance = inject<Ref<ChatInstance>>("chatInstance");
19
+ const vueInstance: any = getCurrentInstance();
20
+ const msgComposerData = vueInstance?.parent?.type?.name === "CometChatMessageComposer" && vueInstance?.parent?.setupState;
21
+ const msgComposerProps = vueInstance?.parent?.type?.name === "CometChatMessageComposer" && vueInstance?.parent?.props;
22
+
23
+ function sendMessage() {
24
+ if (!msgComposerData) return false;
25
+ if (!chatInstance?.value.captionedAttachment?.fileObject) return false;
26
+ if (!msgComposerProps?.group && !msgComposerProps?.user) return false;
27
+
28
+ const receiverId = msgComposerProps?.group?.getGuid() || msgComposerProps?.user?.getUid();
29
+ const receiverType = msgComposerProps?.group ? "group" : "user";
30
+ const fileObject = chatInstance.value.captionedAttachment.fileObject;
31
+ const caption = msgComposerData.messageText;
32
+
33
+ const sendMessageResult = sendMediaMessage(msgComposerProps, fileObject, receiverId, receiverType, "image", caption);
34
+ if (sendMessageResult) {
35
+ msgComposerData.showSendButton = false;
36
+ // Clear text input
37
+ msgComposerData.messageText = "";
38
+ msgComposerData.textRef = "";
39
+ msgComposerData.inputRef?.emptyInputField();
40
+ return true;
41
+ }
42
+ return false;
43
+ }
44
+
45
+ function sendMessageOnEnter(textEnteredEvent: CustomEventInit) {
46
+ if (!msgComposerData) return;
47
+ msgComposerData.messageText = textEnteredEvent.detail.value;
48
+ if (sendMessage()) {
49
+ msgComposerData.showMentionsCountWarning = false;
50
+ msgComposerData.showListForMentions = false;
51
+ }
52
+ }
53
+
54
+ // The following will send the message when **enter is pressed** in the input box and the input box **is not empty**.
55
+ const originalSendMessageOnEnter = msgComposerData?.sendMessageOnEnter;
56
+ onMounted(() => {
57
+ if (!msgComposerData) return;
58
+ msgComposerData.sendMessageOnEnter = sendMessageOnEnter;
59
+ });
60
+ onBeforeUnmount(() => {
61
+ msgComposerData.sendMessageOnEnter = originalSendMessageOnEnter;
62
+ });
63
+
64
+ if (chatInstance) {
65
+ // Here we add an additional onKeyDown event on the input field that sends our image when pressing Enter if the input field **is empty**.
66
+ // We need this in addition to hooking into the "cc-text-input-entered" event because that event isn't triggered if the input field is empty.
67
+ const ccTextInputs = document.querySelectorAll(`.${chatInstance.value.componentId} cometchat-text-input .messageinput-input`);
68
+ const ccTextInput = ccTextInputs[ccTextInputs.length - 1];
69
+ (ccTextInput as Element & {onkeydown: Function}).onkeydown = (function(onkeydown){
70
+ return function(this: any, e: KeyboardEvent) {
71
+ if (chatInstance.value.captionedAttachment?.caption?.trim() == "" && e.keyCode === 13 && !e.shiftKey) sendMessage();
72
+ else if (onkeydown) return onkeydown.bind(this)(e);
73
+ }
74
+ })((ccTextInput as Element & {onkeydown: Function}).onkeydown);
75
+ }
76
+
77
+ </script>
78
+
79
+ <template>
80
+ <div class="cc-messagecomposer-wrapper__sendbutton" ref="sendButton" :style="{ 'background': !msgComposerData || msgComposerProps ? '#f00' : 'unset'}">
81
+ <cometchat-button :iconURL="sendButtonIconURL" :buttonStyle="sendButtonStyle"
82
+ :hoverText="localize('SEND_MESSAGE')" @click=sendMessage() />
83
+ </div>
84
+ </template>
85
+
86
+ <style scoped>
87
+ .cc-messagecomposer-wrapper__sendbutton {
88
+ margin: 0 5px
89
+ }
90
+ </style>
@@ -11,7 +11,7 @@ import {
11
11
  inject,
12
12
  Ref,
13
13
  onBeforeUnmount,
14
- onBeforeMount,
14
+ onMounted,
15
15
  } from "vue";
16
16
  import { ChatInstance } from "../chat/types";
17
17
  import { ReplyPreviewStyle } from "../chat/replyStyle";
@@ -27,6 +27,7 @@ const props = defineProps({
27
27
 
28
28
  const chatInstance = inject<Ref<ChatInstance>>("chatInstance");
29
29
  const vueInstance: any = getCurrentInstance();
30
+ const msgComposerData = vueInstance?.parent?.type?.name === "CometChatMessageComposer" && vueInstance?.parent?.setupState;
30
31
 
31
32
  const { theme } = inject("theme", { theme: ref(new CometChatTheme({})) });
32
33
  const previewStyle = new ReplyPreviewStyle(props.previewStyle, theme.value);
@@ -53,11 +54,21 @@ const onMessageSend = CometChatMessageEvents.ccMessageSent.subscribe(
53
54
  }
54
55
  );
55
56
 
56
- onBeforeMount(() => {
57
- vueInstance?.parent?.setupState?.closePreview();
57
+ onMounted(() => {
58
+ if (msgComposerData) {
59
+ msgComposerData.actionSheetRef.style.display="none";
60
+ msgComposerData.inputRef.querySelector(".messageinput-input").focus()
61
+ if (msgComposerData.messageToBeEdited) {
62
+ msgComposerData.messageToBeEdited = null;
63
+ msgComposerData.messageText = "";
64
+ msgComposerData.textRef = "";
65
+ msgComposerData.inputRef?.emptyInputField();
66
+ }
67
+ }
58
68
  });
59
69
 
60
70
  onBeforeUnmount(() => {
71
+ msgComposerData.actionSheetRef.style.removeProperty("display");
61
72
  onMessageEditStart.unsubscribe();
62
73
  onMessageSend.unsubscribe();
63
74
  });