Chore: Cleanup attachment handling for Facebook & Whatsapp (#1051)

* Chore: Enable file upload for facebook messenger
* Chore: Fix attachments
* Chore: Fix Specs
* Fix ReplyBox file attachment logic
* Set default value for message

Co-authored-by: Pranav Raj Sreepuram <pranavrajs@gmail.com>
This commit is contained in:
Sojan Jose 2020-07-17 00:32:32 +05:30 committed by GitHub
parent 196741d975
commit a18d54b706
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 212 additions and 108 deletions

View file

@ -15,9 +15,9 @@
/>
<resizable-text-area
ref="messageInput"
v-model="message"
v-model.trim="message"
class="input"
:placeholder="$t(messagePlaceHolder())"
:placeholder="messagePlaceHolder"
:min-height="4"
@focus="onFocus"
@blur="onBlur"
@ -25,46 +25,43 @@
<file-upload
v-if="showFileUpload"
:size="4096 * 4096"
accept="jpg,jpeg,png,mp3,ogg,amr,pdf,mp4"
accept="image/*, application/pdf, audio/mpeg, video/mp4, audio/ogg"
@input-file="onFileUpload"
>
<i
v-if="!isUploading.image"
class="icon ion-android-attach attachment"
/>
<woot-spinner v-if="isUploading.image" />
<i v-if="!isUploading" class="icon ion-android-attach attachment" />
<woot-spinner v-if="isUploading" />
</file-upload>
<i
class="icon ion-happy-outline"
:class="{ active: showEmojiPicker }"
@click="toggleEmojiPicker()"
@click="toggleEmojiPicker"
/>
</div>
<div class="reply-box__bottom">
<ul class="tabs">
<li class="tabs-title" :class="{ 'is-active': !isPrivate }">
<a href="#" @click="makeReply">{{
<a href="#" @click="setReplyMode">{{
$t('CONVERSATION.REPLYBOX.REPLY')
}}</a>
</li>
<li class="tabs-title is-private" :class="{ 'is-active': isPrivate }">
<a href="#" @click="makePrivate">{{
<a href="#" @click="setPrivateReplyMode">{{
$t('CONVERSATION.REPLYBOX.PRIVATE_NOTE')
}}</a>
</li>
<li v-if="message.length" class="tabs-title message-length">
<a :class="{ 'message-error': message.length > maxLength - 40 }">
{{ message.length }} / {{ maxLength }}
</a>
<a :class="{ 'message-error': isMessageLengthReachingThreshold }">{{
characterCountIndicator
}}</a>
</li>
</ul>
<button
type="button"
class="button send-button"
:disabled="disableButton()"
:disabled="isReplyButtonDisabled"
:class="{
disabled: message.length === 0 || message.length > maxLength,
disabled: isReplyButtonDisabled,
warning: isPrivate,
}"
@click="sendMessage"
@ -93,6 +90,12 @@ import FileUpload from 'vue-upload-component';
import EmojiInput from '../emoji/EmojiInput';
import CannedResponse from './CannedResponse';
import ResizableTextArea from 'shared/components/ResizableTextArea';
import {
isEscape,
isEnter,
hasPressedShift,
} from 'shared/helpers/KeyboardHelpers';
import { MESSAGE_MAX_LENGTH } from 'shared/helpers/MessageTypeHelper';
export default {
components: {
@ -109,19 +112,38 @@ export default {
isFocused: false,
showEmojiPicker: false,
showCannedResponsesList: false,
isUploading: {
audio: false,
video: false,
image: false,
},
isUploading: false,
};
},
computed: {
...mapGetters({
currentChat: 'getSelectedChat',
}),
...mapGetters({ currentChat: 'getSelectedChat' }),
inboxId() {
return this.currentChat.inbox_id;
},
inbox() {
return this.$store.getters['inboxes/getInbox'](this.inboxId);
},
messagePlaceHolder() {
return this.isPrivate
? this.$t('CONVERSATION.FOOTER.PRIVATE_MSG_INPUT')
: this.$t('CONVERSATION.FOOTER.MSG_INPUT');
},
isMessageLengthReachingThreshold() {
return this.message.length > this.maxLength - 40;
},
characterCountIndicator() {
return `${this.message.length} / ${this.maxLength}`;
},
isReplyButtonDisabled() {
const isMessageEmpty = !this.message.replace(/\n/g, '').length;
return (
isMessageEmpty ||
this.message.length === 0 ||
this.message.length > this.maxLength
);
},
channelType() {
return this.currentChat.meta.channel;
return this.inbox.channel_type;
},
conversationType() {
const { additional_attributes: additionalAttributes } = this.currentChat;
@ -129,18 +151,52 @@ export default {
return type || '';
},
maxLength() {
if (this.channelType === 'Channel::FacebookPage') {
return 640;
if (this.isPrivate) {
return MESSAGE_MAX_LENGTH.GENERAL;
}
if (this.channelType === 'Channel::TwitterProfile') {
if (this.isAFacebookInbox) {
return MESSAGE_MAX_LENGTH.FACEBOOK;
}
if (this.isATwilioSMSChannel) {
return MESSAGE_MAX_LENGTH.TWILIO_SMS;
}
if (this.isATwitterInbox) {
if (this.conversationType === 'tweet') {
return 280;
return MESSAGE_MAX_LENGTH.TWEET;
}
}
return 10000;
return MESSAGE_MAX_LENGTH.GENERAL;
},
isATwitterInbox() {
return this.channelType === 'Channel::TwitterProfile';
},
isAFacebookInbox() {
return this.channelType === 'Channel::FacebookPage';
},
isAWebWidgetInbox() {
return this.channelType === 'Channel::WebWidget';
},
isATwilioSMSChannel() {
const { phone_number: phoneNumber = '' } = this.inbox;
return (
this.channelType === 'Channel::TwilioSms' &&
!phoneNumber.startsWith('whatsapp')
);
},
isATwilioWhatsappChannel() {
const { phone_number: phoneNumber = '' } = this.inbox;
return (
this.channelType === 'Channel::TwilioSms' &&
phoneNumber.startsWith('whatsapp')
);
},
showFileUpload() {
return this.channelType === 'Channel::WebWidget';
return (
this.isAWebWidgetInbox ||
this.isAFacebookInbox ||
this.isATwilioWhatsappChannel
);
},
replyButtonLabel() {
if (this.isPrivate) {
@ -158,20 +214,18 @@ export default {
},
},
watch: {
message(val) {
message(updatedMessage) {
if (this.isPrivate) {
return;
}
const isSlashCommand = val[0] === '/';
const hasNextWord = val.includes(' ');
const isSlashCommand = updatedMessage[0] === '/';
const hasNextWord = updatedMessage.includes(' ');
const isShortCodeActive = isSlashCommand && !hasNextWord;
if (isShortCodeActive) {
this.showCannedResponsesList = true;
if (val.length > 1) {
const searchKey = val.substr(1, val.length);
this.$store.dispatch('getCannedResponse', {
searchKey,
});
if (updatedMessage.length > 1) {
const searchKey = updatedMessage.substr(1, updatedMessage.length);
this.$store.dispatch('getCannedResponse', { searchKey });
} else {
this.$store.dispatch('getCannedResponse');
}
@ -188,26 +242,18 @@ export default {
},
methods: {
handleKeyEvents(e) {
if (this.isEscape(e)) {
if (isEscape(e)) {
this.hideEmojiPicker();
this.hideCannedResponse();
} else if (this.isEnter(e)) {
if (!e.shiftKey) {
} else if (isEnter(e)) {
if (!hasPressedShift(e)) {
e.preventDefault();
this.sendMessage();
}
}
},
isEnter(e) {
return e.keyCode === 13;
},
isEscape(e) {
return e.keyCode === 27; // ESCAPE
},
async sendMessage() {
const isMessageEmpty = !this.message.replace(/\n/g, '').length;
if (isMessageEmpty) return;
if (this.message.length > this.maxLength) {
if (this.isReplyButtonDisabled) {
return;
}
const newMessage = this.message;
@ -231,11 +277,11 @@ export default {
this.message = message;
}, 100);
},
makePrivate() {
setPrivateReplyMode() {
this.isPrivate = true;
this.$refs.messageInput.focus();
},
makeReply() {
setReplyMode() {
this.isPrivate = false;
this.$refs.messageInput.focus();
},
@ -258,7 +304,6 @@ export default {
hideCannedResponse() {
this.showCannedResponsesList = false;
},
onBlur() {
this.isFocused = false;
this.toggleTyping('off');
@ -267,9 +312,8 @@ export default {
this.isFocused = true;
this.toggleTyping('on');
},
toggleTyping(status) {
if (this.channelType === 'Channel::WebWidget' && !this.isPrivate) {
if (this.isAWebWidgetInbox && !this.isPrivate) {
const conversationId = this.currentChat.id;
this.$store.dispatch('conversationTypingStatus/toggleTyping', {
status,
@ -277,35 +321,19 @@ export default {
});
}
},
disableButton() {
const messageHasOnlyNewLines = !this.message.replace(/\n/g, '').length;
return (
this.message.length === 0 ||
this.message.length > 640 ||
messageHasOnlyNewLines
);
},
messagePlaceHolder() {
const placeHolder = this.isPrivate
? 'CONVERSATION.FOOTER.PRIVATE_MSG_INPUT'
: 'CONVERSATION.FOOTER.MSG_INPUT';
return placeHolder;
},
onFileUpload(file) {
if (!file) {
return;
}
this.isUploading.image = true;
this.isUploading = true;
this.$store
.dispatch('sendAttachment', [this.currentChat.id, { file: file.file }])
.then(() => {
this.isUploading.image = false;
this.isUploading = false;
this.$emit('scrollToMessage');
})
.catch(() => {
this.isUploading.image = false;
this.isUploading = false;
this.$emit('scrollToMessage');
});
},

View file

@ -12,6 +12,10 @@ const initialSelectedChat = {
status: null,
muted: false,
seen: false,
inbox_id: null,
additional_attributes: {
type: '',
},
dataFetched: false,
};
const state = {

View file

@ -0,0 +1,11 @@
export const isEnter = e => {
return e.keyCode === 13;
};
export const isEscape = e => {
return e.keyCode === 27;
};
export const hasPressedShift = e => {
return e.shiftKey;
};

View file

@ -2,7 +2,7 @@ import { escapeHtml } from './HTMLSanitizer';
class MessageFormatter {
constructor(message) {
this.message = escapeHtml(message) || '';
this.message = escapeHtml(message || '') || '';
}
formatMessage() {

View file

@ -1,3 +1,10 @@
export const isAFormMessage = message => message.content_type === 'form';
export const isASubmittedFormMessage = (message = {}) =>
isAFormMessage(message) && !!message.content_attributes?.submitted_values;
export const MESSAGE_MAX_LENGTH = {
GENERAL: 10000,
FACEBOOK: 640,
TWILIO_SMS: 160,
TWEET: 280,
};

View file

@ -0,0 +1,21 @@
import { isEnter, isEscape, hasPressedShift } from '../KeyboardHelpers';
describe('#KeyboardHelpers', () => {
describe('#isEnter', () => {
it('return correct values', () => {
expect(isEnter({ keyCode: 13 })).toEqual(true);
});
});
describe('#isEscape', () => {
it('return correct values', () => {
expect(isEscape({ keyCode: 27 })).toEqual(true);
});
});
describe('#hasPressedShift', () => {
it('return correct values', () => {
expect(hasPressedShift({ shiftKey: true })).toEqual(true);
});
});
});