chore: Allow more filetypes in uploads (#3557)

- Allowing the ability to upload more common file types like zip, Docx etc
- Fallback for image bubble when the image URL isn't available

fixes: #3270

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Nithin David <1277421+nithindavid@users.noreply.github.com>
This commit is contained in:
Sojan Jose 2021-12-20 23:50:37 +05:30 committed by GitHub
parent 76e8acd3c6
commit 6fe5484119
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 99 additions and 20 deletions

View file

@ -1,16 +1,29 @@
module FileTypeHelper module FileTypeHelper
# NOTE: video, audio, image, etc are filetypes previewable in frontend
def file_type(content_type) def file_type(content_type)
return :image if [ return :image if image_file?(content_type)
'image/jpeg', return :video if video_file?(content_type)
'image/png',
'image/gif',
'image/tiff',
'image/bmp'
].include?(content_type)
return :video if content_type.include?('video/')
return :audio if content_type.include?('audio/') return :audio if content_type.include?('audio/')
:file :file
end end
def image_file?(content_type)
[
'image/jpeg',
'image/png',
'image/gif',
'image/bmp',
'image/webp'
].include?(content_type)
end
def video_file?(content_type)
[
'video/ogg',
'video/mp4',
'video/webm',
'video/quicktime'
].include?(content_type)
end
end end

View file

@ -45,6 +45,9 @@
// 1. Global // 1. Global
// --------- // ---------
// Disable contrast warnings in Foundation.
$contrast-warnings: false;
$global-font-size: 10px; $global-font-size: 10px;
$global-width: 100%; $global-width: 100%;
$global-lineheight: 1.5; $global-lineheight: 1.5;

View file

@ -15,7 +15,7 @@
<file-upload <file-upload
ref="upload" ref="upload"
:size="4096 * 4096" :size="4096 * 4096"
accept="image/png, image/jpeg, image/gif, image/bmp, image/tiff, application/pdf, audio/mpeg, video/mp4, audio/ogg, text/csv" :accept="allowedFileTypes"
:drop="true" :drop="true"
:drop-directory="false" :drop-directory="false"
@input-file="onFileUpload" @input-file="onFileUpload"
@ -84,6 +84,7 @@ import {
hasPressedAltAndAKey, hasPressedAltAndAKey,
} from 'shared/helpers/KeyboardHelpers'; } from 'shared/helpers/KeyboardHelpers';
import eventListenerMixins from 'shared/mixins/eventListenerMixins'; import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import { ALLOWED_FILE_TYPES } from 'shared/constants/messages';
import { REPLY_EDITOR_MODES } from './constants'; import { REPLY_EDITOR_MODES } from './constants';
export default { export default {
@ -161,6 +162,9 @@ export default {
showAttachButton() { showAttachButton() {
return this.showFileUpload || this.isNote; return this.showFileUpload || this.isNote;
}, },
allowedFileTypes() {
return ALLOWED_FILE_TYPES;
},
}, },
methods: { methods: {
handleKeyEvents(e) { handleKeyEvents(e) {

View file

@ -24,9 +24,10 @@
<div v-if="!isPending && hasAttachments"> <div v-if="!isPending && hasAttachments">
<div v-for="attachment in data.attachments" :key="attachment.id"> <div v-for="attachment in data.attachments" :key="attachment.id">
<bubble-image <bubble-image
v-if="attachment.file_type === 'image'" v-if="attachment.file_type === 'image' && !hasImageError"
:url="attachment.data_url" :url="attachment.data_url"
:readable-time="readableTime" :readable-time="readableTime"
@error="onImageLoadError"
/> />
<audio v-else-if="attachment.file_type === 'audio'" controls> <audio v-else-if="attachment.file_type === 'audio'" controls>
<source :src="attachment.data_url" /> <source :src="attachment.data_url" />
@ -133,6 +134,7 @@ export default {
data() { data() {
return { return {
showContextMenu: false, showContextMenu: false,
hasImageError: false,
}; };
}, },
computed: { computed: {
@ -287,12 +289,20 @@ export default {
return messageType ? 'right' : 'left'; return messageType ? 'right' : 'left';
}, },
}, },
watch: {
data() {
this.hasImageError = false;
},
},
mounted() {
this.hasImageError = false;
},
methods: { methods: {
hasMediaAttachment(type) { hasMediaAttachment(type) {
if (this.hasAttachments && this.data.attachments.length > 0) { if (this.hasAttachments && this.data.attachments.length > 0) {
const { attachments = [{}] } = this.data; const { attachments = [{}] } = this.data;
const { file_type: fileType } = attachments[0]; const { file_type: fileType } = attachments[0];
return fileType === type; return fileType === type && !this.hasImageError;
} }
return false; return false;
}, },
@ -317,6 +327,9 @@ export default {
this.showAlert(this.$t('CONTACT_PANEL.COPY_SUCCESSFUL')); this.showAlert(this.$t('CONTACT_PANEL.COPY_SUCCESSFUL'));
this.showContextMenu = false; this.showContextMenu = false;
}, },
onImageLoadError() {
this.hasImageError = true;
},
}, },
}; };
</script> </script>

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="image message-text__wrap"> <div class="image message-text__wrap">
<img :src="url" @click="onClick" /> <img :src="url" @click="onClick" @error="onImgError()" />
<woot-modal :full-width="true" :show.sync="show" :on-close="onClose"> <woot-modal :full-width="true" :show.sync="show" :on-close="onClose">
<img :src="url" class="modal-image" /> <img :src="url" class="modal-image" />
</woot-modal> </woot-modal>
@ -9,6 +9,7 @@
<script> <script>
export default { export default {
components: {},
props: { props: {
url: { url: {
type: String, type: String,
@ -27,6 +28,9 @@ export default {
onClick() { onClick() {
this.show = true; this.show = true;
}, },
onImgError() {
this.$emit('error');
},
}, },
}; };
</script> </script>

View file

@ -32,7 +32,11 @@
:label="inboxNameLabel" :label="inboxNameLabel"
:placeholder="inboxNamePlaceHolder" :placeholder="inboxNamePlaceHolder"
/> />
<label for="toggle-business-hours" class="toggle-input-wrap" v-if="isATwitterInbox"> <label
v-if="isATwitterInbox"
for="toggle-business-hours"
class="toggle-input-wrap"
>
<input <input
v-model="tweetsEnabled" v-model="tweetsEnabled"
type="checkbox" type="checkbox"

View file

@ -13,6 +13,17 @@ export const MESSAGE_TYPE = {
// Size in mega bytes // Size in mega bytes
export const MAXIMUM_FILE_UPLOAD_SIZE = 40; export const MAXIMUM_FILE_UPLOAD_SIZE = 40;
export const ALLOWED_FILE_TYPES =
'image/*,' +
'audio/*,' +
'video/*,' +
'.3gpp,' +
'text/csv, text/plain, application/json, application/pdf, text/rtf,' +
'application/zip, application/x-7z-compressed application/vnd.rar application/x-tar,' +
'application/msword, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/vnd.oasis.opendocument.text,' +
'application/vnd.openxmlformats-officedocument.presentationml.presentation, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,' +
'application/vnd.openxmlformats-officedocument.wordprocessingml.document,';
export const CSAT_RATINGS = [ export const CSAT_RATINGS = [
{ {
key: 'disappointed', key: 'disappointed',

View file

@ -1,7 +1,7 @@
<template> <template>
<file-upload <file-upload
:size="4096 * 2048" :size="4096 * 2048"
accept="image/*, application/pdf, audio/mpeg, video/mp4, audio/ogg, text/csv" :accept="allowedFileTypes"
@input-file="onFileUpload" @input-file="onFileUpload"
> >
<button class="icon-button flex items-center justify-center"> <button class="icon-button flex items-center justify-center">
@ -15,7 +15,10 @@
import FileUpload from 'vue-upload-component'; import FileUpload from 'vue-upload-component';
import Spinner from 'shared/components/Spinner.vue'; import Spinner from 'shared/components/Spinner.vue';
import { checkFileSizeLimit } from 'shared/helpers/FileHelper'; import { checkFileSizeLimit } from 'shared/helpers/FileHelper';
import { MAXIMUM_FILE_UPLOAD_SIZE } from 'shared/constants/messages'; import {
MAXIMUM_FILE_UPLOAD_SIZE,
ALLOWED_FILE_TYPES,
} from 'shared/constants/messages';
import { BUS_EVENTS } from 'shared/constants/busEvents'; import { BUS_EVENTS } from 'shared/constants/busEvents';
import FluentIcon from 'shared/components/FluentIcon/Index.vue'; import FluentIcon from 'shared/components/FluentIcon/Index.vue';
export default { export default {
@ -33,6 +36,9 @@ export default {
fileUploadSizeLimit() { fileUploadSizeLimit() {
return MAXIMUM_FILE_UPLOAD_SIZE; return MAXIMUM_FILE_UPLOAD_SIZE;
}, },
allowedFileTypes() {
return ALLOWED_FILE_TYPES;
},
}, },
methods: { methods: {
getFileType(fileType) { getFileType(fileType) {

View file

@ -17,6 +17,18 @@
class Attachment < ApplicationRecord class Attachment < ApplicationRecord
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
ACCEPTABLE_FILE_TYPES = %w[
text/csv text/plain text/rtf
application/json application/pdf
application/zip application/x-7z-compressed application/vnd.rar application/x-tar
application/msword application/vnd.ms-excel application/vnd.ms-powerpoint application/rtf
application/vnd.oasis.opendocument.text
application/vnd.openxmlformats-officedocument.presentationml.presentation
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
application/vnd.openxmlformats-officedocument.wordprocessingml.document
].freeze
belongs_to :account belongs_to :account
belongs_to :message belongs_to :message
has_one_attached :file has_one_attached :file
@ -90,10 +102,19 @@ class Attachment < ApplicationRecord
def acceptable_file def acceptable_file
return unless should_validate_file? return unless should_validate_file?
errors.add(:file, 'is too big') if file.byte_size > 40.megabytes validate_file_size(file.byte_size)
validate_file_content_type(file.content_type)
end
acceptable_types = ['image/png', 'image/jpeg', 'image/gif', 'image/bmp', 'image/tiff', 'application/pdf', 'audio/mpeg', 'video/mp4', 'audio/ogg', def validate_file_content_type(file_content_type)
'text/csv'].freeze errors.add(:file, 'type not supported') unless media_file?(file_content_type) || ACCEPTABLE_FILE_TYPES.include?(file_content_type)
errors.add(:file, 'filetype not supported') unless acceptable_types.include?(file.content_type) end
def validate_file_size(byte_size)
errors.add(:file, 'size is too big') if byte_size > 40.megabytes
end
def media_file?(file_content_type)
file_content_type.start_with?('image/', 'video/', 'audio/')
end end
end end