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:
parent
76e8acd3c6
commit
6fe5484119
9 changed files with 99 additions and 20 deletions
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue