From 7fcd2d0e85449ae3ecc4d1086f92335bca452941 Mon Sep 17 00:00:00 2001 From: Nithin David Thomas Date: Thu, 2 Apr 2020 12:28:38 +0530 Subject: [PATCH] Feature: Support file type messages on widget and dashboard (#659) - Adds support for file upload Co-authored-by: Pranav Raj Sreepuram Co-authored-by: Sojan --- .../messages/outgoing/normal_builder.rb | 6 +- .../api/v1/widget/messages_controller.rb | 16 +++- app/helpers/file_type_helper.rb | 14 +++ app/javascript/dashboard/api/inbox/message.js | 3 +- .../widgets/conversation/Message.vue | 23 ++--- .../widgets/conversation/ReplyBox.vue | 16 ++-- .../widgets/conversation/bubble/File.vue | 71 ++++++++++++++ app/javascript/dashboard/i18n/en.js | 4 + .../i18n/locale/en/conversation.json | 1 + app/javascript/packs/widget.js | 8 ++ .../shared/helpers/MessageFormatter.js | 3 +- .../helpers/specs/MessageFormatter.spec.js | 2 +- .../widget/components/AgentMessage.vue | 15 ++- .../widget/components/ChatAttachment.vue | 7 +- .../widget/components/ChatInputWrap.vue | 18 ++-- .../widget/components/FileBubble.vue | 93 +++++++++++++++++++ .../widget/components/ImageBubble.vue | 7 +- .../widget/components/UserMessage.vue | 26 ++++-- .../widget/components/UserMessageBubble.vue | 1 - app/javascript/widget/i18n/en.js | 8 ++ app/javascript/widget/i18n/index.js | 5 + .../widget/store/modules/conversation.js | 23 +++-- .../specs/conversation/actions.spec.js | 4 +- .../specs/conversation/mutations.spec.js | 19 +++- .../v1/widget/messages/create.json.jbuilder | 10 ++ .../v1/widget/messages/index.json.jbuilder | 2 +- .../conversations/messages_controller_spec.rb | 1 + .../api/v1/widget/messages_controller_spec.rb | 1 + 28 files changed, 338 insertions(+), 69 deletions(-) create mode 100644 app/helpers/file_type_helper.rb create mode 100644 app/javascript/dashboard/components/widgets/conversation/bubble/File.vue create mode 100644 app/javascript/widget/components/FileBubble.vue create mode 100644 app/javascript/widget/i18n/en.js create mode 100644 app/javascript/widget/i18n/index.js create mode 100644 app/views/api/v1/widget/messages/create.json.jbuilder diff --git a/app/builders/messages/outgoing/normal_builder.rb b/app/builders/messages/outgoing/normal_builder.rb index a3c744341..668022e64 100644 --- a/app/builders/messages/outgoing/normal_builder.rb +++ b/app/builders/messages/outgoing/normal_builder.rb @@ -1,4 +1,5 @@ class Messages::Outgoing::NormalBuilder + include ::FileTypeHelper attr_reader :message def initialize(user, conversation, params) @@ -13,7 +14,10 @@ class Messages::Outgoing::NormalBuilder def perform @message = @conversation.messages.build(message_params) if @attachment - @message.attachment = Attachment.new(account_id: message.account_id) + @message.attachment = Attachment.new( + account_id: message.account_id, + file_type: file_type(@attachment[:file]&.content_type) + ) @message.attachment.file.attach(@attachment[:file]) end @message.save diff --git a/app/controllers/api/v1/widget/messages_controller.rb b/app/controllers/api/v1/widget/messages_controller.rb index 2fe76f0eb..809975ab4 100644 --- a/app/controllers/api/v1/widget/messages_controller.rb +++ b/app/controllers/api/v1/widget/messages_controller.rb @@ -10,12 +10,8 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController def create @message = conversation.messages.new(message_params) + build_attachment @message.save! - if params[:message][:attachment].present? - @message.attachment = Attachment.new(account_id: @message.account_id) - @message.attachment.file.attach(params[:message][:attachment][:file]) - end - render json: @message end def update @@ -28,6 +24,16 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController private + def build_attachment + return if params[:message][:attachment].blank? + + @message.attachment = Attachment.new( + account_id: @message.account_id, + file_type: helpers.file_type(params[:message][:attachment][:file]&.content_type) + ) + @message.attachment.file.attach(params[:message][:attachment][:file]) + end + def set_conversation @conversation = ::Conversation.create!(conversation_params) if conversation.nil? end diff --git a/app/helpers/file_type_helper.rb b/app/helpers/file_type_helper.rb new file mode 100644 index 000000000..9f6939565 --- /dev/null +++ b/app/helpers/file_type_helper.rb @@ -0,0 +1,14 @@ +module FileTypeHelper + def file_type(content_type) + return :image if [ + 'image/jpeg', + 'image/png', + 'image/svg+xml', + 'image/gif', + 'image/tiff', + 'image/bmp' + ].include?(content_type) + + :file + end +end diff --git a/app/javascript/dashboard/api/inbox/message.js b/app/javascript/dashboard/api/inbox/message.js index 2e1669ef6..579d97a10 100644 --- a/app/javascript/dashboard/api/inbox/message.js +++ b/app/javascript/dashboard/api/inbox/message.js @@ -20,10 +20,9 @@ class MessageApi extends ApiClient { }); } - sendAttachment([conversationId, { file, file_type }]) { + sendAttachment([conversationId, { file }]) { const formData = new FormData(); formData.append('attachment[file]', file); - formData.append('attachment[file_type]', file_type); return axios({ method: 'post', url: `${this.url}/${conversationId}/messages`, diff --git a/app/javascript/dashboard/components/widgets/conversation/Message.vue b/app/javascript/dashboard/components/widgets/conversation/Message.vue index a398ab52a..7095b2d86 100644 --- a/app/javascript/dashboard/components/widgets/conversation/Message.vue +++ b/app/javascript/dashboard/components/widgets/conversation/Message.vue @@ -7,18 +7,11 @@ :url="data.attachment.data_url" :readable-time="readableTime" /> - -

- + + + diff --git a/app/javascript/dashboard/i18n/en.js b/app/javascript/dashboard/i18n/en.js index ae7e2751b..94679eb09 100644 --- a/app/javascript/dashboard/i18n/en.js +++ b/app/javascript/dashboard/i18n/en.js @@ -11,6 +11,10 @@ export default { BUTTON_TEXT: 'Copy', COPY_SUCCESSFUL: 'Code copied to clipboard successfully', }, + FILE_BUBBLE: { + DOWNLOAD: 'Download', + UPLOADING: 'Uploading...', + }, }, CONFIRM_EMAIL: 'Verifying...', SETTINGS: { diff --git a/app/javascript/dashboard/i18n/locale/en/conversation.json b/app/javascript/dashboard/i18n/locale/en/conversation.json index 894f2db19..b07e4e1f1 100644 --- a/app/javascript/dashboard/i18n/locale/en/conversation.json +++ b/app/javascript/dashboard/i18n/locale/en/conversation.json @@ -9,6 +9,7 @@ "CLICK_HERE": "Click here", "LOADING_INBOXES": "Loading inboxes", "LOADING_CONVERSATIONS": "Loading Conversations", + "DOWNLOAD": "Download", "HEADER": { "RESOLVE_ACTION": "Resolve", "REOPEN_ACTION": "Reopen", diff --git a/app/javascript/packs/widget.js b/app/javascript/packs/widget.js index 856c33e05..ed9af223a 100644 --- a/app/javascript/packs/widget.js +++ b/app/javascript/packs/widget.js @@ -1,12 +1,20 @@ import Vue from 'vue'; import Vuelidate from 'vuelidate'; +import VueI18n from 'vue-i18n'; import store from '../widget/store'; import App from '../widget/App.vue'; import router from '../widget/router'; import ActionCableConnector from '../widget/helpers/actionCable'; +import i18n from '../widget/i18n'; +Vue.use(VueI18n); Vue.use(Vuelidate); +Vue.config.lang = 'en'; +Object.keys(i18n).forEach(lang => { + Vue.locale(lang, i18n[lang]); +}); + Vue.config.productionTip = false; window.onload = () => { window.WOOT_WIDGET = new Vue({ diff --git a/app/javascript/shared/helpers/MessageFormatter.js b/app/javascript/shared/helpers/MessageFormatter.js index 32aeef48f..2edd5576b 100644 --- a/app/javascript/shared/helpers/MessageFormatter.js +++ b/app/javascript/shared/helpers/MessageFormatter.js @@ -15,7 +15,8 @@ class MessageFormatter { const urlRegex = /(https?:\/\/[^\s]+)/g; return this.message.replace( urlRegex, - url => `${url}` + url => + `${url}` ); } diff --git a/app/javascript/shared/helpers/specs/MessageFormatter.spec.js b/app/javascript/shared/helpers/specs/MessageFormatter.spec.js index f6ffb34f8..847f9efd9 100644 --- a/app/javascript/shared/helpers/specs/MessageFormatter.spec.js +++ b/app/javascript/shared/helpers/specs/MessageFormatter.spec.js @@ -6,7 +6,7 @@ describe('#MessageFormatter', () => { const message = 'Chatwoot is an opensource tool\nSee more at https://www.chatwoot.com'; expect(new MessageFormatter(message).formattedMessage).toEqual( - 'Chatwoot is an opensource tool
See more at https://www.chatwoot.com' + 'Chatwoot is an opensource tool
See more at https://www.chatwoot.com' ); }); }); diff --git a/app/javascript/widget/components/AgentMessage.vue b/app/javascript/widget/components/AgentMessage.vue index d0ffd1f3c..109a396b1 100755 --- a/app/javascript/widget/components/AgentMessage.vue +++ b/app/javascript/widget/components/AgentMessage.vue @@ -17,8 +17,13 @@ :message-type="messageType" :message="message.content" /> -
+
+ - + @@ -23,12 +23,15 @@ export default { return { isUploading: false }; }, methods: { + getFileType(fileType) { + return fileType.includes('image') ? 'image' : 'file'; + }, async onFileUpload(file) { this.isUploading = true; try { const thumbUrl = window.URL.createObjectURL(file.file); await this.onAttach({ - file_type: file.type, + fileType: this.getFileType(file.type), file: file.file, thumbUrl, }); diff --git a/app/javascript/widget/components/ChatInputWrap.vue b/app/javascript/widget/components/ChatInputWrap.vue index a359a78b1..82e68ac94 100755 --- a/app/javascript/widget/components/ChatInputWrap.vue +++ b/app/javascript/widget/components/ChatInputWrap.vue @@ -1,6 +1,6 @@