Files
leadchat/app/javascript/widget/components/ChatInputWrap.vue
Muhsin Keloth 116ed54c7e fix: Prioritize SDK enableFileUpload flag when explicitly set (#13091)
### Problem
Currently, the attachment button visibility in the widget uses both the
SDK's `enableFileUpload` flag AND the inbox's attachment settings with
an AND condition. This creates an issue for users who want to control
attachments solely through inbox settings, since the SDK flag defaults
to `true` even when not explicitly provided.

  **Before:**
- SDK not set → `enableFileUpload: true` (default) + inbox setting =
attachment shown only if both true
- SDK set to false → `enableFileUpload: false` + inbox setting =
attachment always hidden
- SDK set to true → `enableFileUpload: true` + inbox setting =
attachment shown only if both true
  
This meant users couldn't rely solely on inbox settings when the SDK
flag wasn't explicitly provided.

  ### Solution

Changed the logic to prioritize explicit SDK configuration when
provided, and fall back to inbox settings when not provided:

  **After:**
  - SDK not set → `enableFileUpload: undefined` → use inbox setting only
- SDK set to false → `enableFileUpload: false` → hide attachment (SDK
controls)
- SDK set to true → `enableFileUpload: true` → show attachment (SDK
controls)

---------

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2025-12-17 19:03:54 +05:30

196 lines
5.0 KiB
Vue
Executable File

<script>
import { mapGetters } from 'vuex';
import ChatAttachmentButton from 'widget/components/ChatAttachment.vue';
import ChatSendButton from 'widget/components/ChatSendButton.vue';
import { useAttachments } from '../composables/useAttachments';
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
import ResizableTextArea from 'shared/components/ResizableTextArea.vue';
import EmojiInput from 'shared/components/emoji/EmojiInput.vue';
export default {
name: 'ChatInputWrap',
components: {
ChatAttachmentButton,
ChatSendButton,
EmojiInput,
FluentIcon,
ResizableTextArea,
},
props: {
onSendMessage: {
type: Function,
default: () => {},
},
onSendAttachment: {
type: Function,
default: () => {},
},
},
setup() {
const {
canHandleAttachments,
shouldShowEmojiPicker,
hasEmojiPickerEnabled,
} = useAttachments();
return {
canHandleAttachments,
shouldShowEmojiPicker,
hasEmojiPickerEnabled,
};
},
data() {
return {
userInput: '',
showEmojiPicker: false,
isFocused: false,
};
},
computed: {
...mapGetters({
widgetColor: 'appConfig/getWidgetColor',
isWidgetOpen: 'appConfig/getIsWidgetOpen',
shouldShowEmojiPicker: 'appConfig/getShouldShowEmojiPicker',
}),
showAttachment() {
return this.canHandleAttachments && this.userInput.length === 0;
},
showSendButton() {
return this.userInput.length > 0;
},
},
watch: {
isWidgetOpen(isWidgetOpen) {
if (isWidgetOpen) {
this.focusInput();
}
},
},
unmounted() {
document.removeEventListener('keypress', this.handleEnterKeyPress);
},
mounted() {
document.addEventListener('keypress', this.handleEnterKeyPress);
if (this.isWidgetOpen) {
this.focusInput();
}
},
methods: {
onBlur() {
this.isFocused = false;
},
onFocus() {
this.isFocused = true;
},
handleButtonClick() {
if (this.userInput && this.userInput.trim()) {
this.onSendMessage(this.userInput);
}
this.userInput = '';
this.focusInput();
},
handleEnterKeyPress(e) {
if (e.keyCode === 13 && !e.shiftKey) {
e.preventDefault();
this.handleButtonClick();
}
},
toggleEmojiPicker() {
this.showEmojiPicker = !this.showEmojiPicker;
},
hideEmojiPicker(e) {
if (this.showEmojiPicker) {
e.stopPropagation();
this.toggleEmojiPicker();
}
},
emojiOnClick(emoji) {
this.userInput = `${this.userInput}${emoji} `;
},
onTypingOff() {
this.toggleTyping('off');
},
onTypingOn() {
this.toggleTyping('on');
},
toggleTyping(typingStatus) {
this.$store.dispatch('conversation/toggleUserTyping', { typingStatus });
},
focusInput() {
this.$refs.chatInput.focus();
},
},
};
</script>
<template>
<div
class="items-center flex ltr:pl-3 rtl:pr-3 ltr:pr-2 rtl:pl-2 rounded-[7px] transition-all duration-200 bg-n-background !shadow-[0_0_0_1px,0_0_2px_3px]"
:class="{
'!shadow-n-brand dark:!shadow-n-brand': isFocused,
'!shadow-n-strong dark:!shadow-n-strong': !isFocused,
}"
@keydown.esc="hideEmojiPicker"
>
<ResizableTextArea
id="chat-input"
ref="chatInput"
v-model="userInput"
:rows="1"
:aria-label="$t('CHAT_PLACEHOLDER')"
:placeholder="$t('CHAT_PLACEHOLDER')"
class="user-message-input reset-base"
@typing-off="onTypingOff"
@typing-on="onTypingOn"
@focus="onFocus"
@blur="onBlur"
/>
<div class="flex items-center ltr:pl-2 rtl:pr-2">
<ChatAttachmentButton
v-if="showAttachment"
class="text-n-slate-12"
:on-attach="onSendAttachment"
/>
<button
v-if="shouldShowEmojiPicker && hasEmojiPickerEnabled"
class="flex items-center justify-center min-h-8 min-w-8"
:aria-label="$t('EMOJI.ARIA_LABEL')"
@click="toggleEmojiPicker"
>
<FluentIcon
icon="emoji"
class="transition-all duration-150"
:class="{
'text-n-slate-12': !showEmojiPicker,
'text-n-brand': showEmojiPicker,
}"
/>
</button>
<EmojiInput
v-if="shouldShowEmojiPicker && showEmojiPicker"
v-on-clickaway="hideEmojiPicker"
:on-click="emojiOnClick"
@keydown.esc="hideEmojiPicker"
/>
<ChatSendButton
v-if="showSendButton"
:color="widgetColor"
@click="handleButtonClick"
/>
</div>
</div>
</template>
<style scoped lang="scss">
.emoji-dialog {
@apply max-w-full ltr:right-5 rtl:right-[unset] rtl:left-5 -top-[302px] before:ltr:right-2.5 before:rtl:right-[unset] before:rtl:left-2.5;
}
.user-message-input {
@apply border-none outline-none w-full placeholder:text-n-slate-10 resize-none h-8 min-h-8 max-h-60 py-1 px-0 my-2 bg-n-background text-n-slate-12 transition-all duration-200;
}
</style>