diff --git a/components/file_attachment.jsx b/components/file_attachment.jsx
index 33bf60ffccd8..6aa9b181c1bf 100644
--- a/components/file_attachment.jsx
+++ b/components/file_attachment.jsx
@@ -5,11 +5,19 @@ import PropTypes from 'prop-types';
import React from 'react';
import {getFileThumbnailUrl, getFileUrl} from 'mattermost-redux/utils/file_utils';
-import Constants, {FileTypes} from 'utils/constants.jsx';
-import * as FileUtils from 'utils/file_utils';
-import * as Utils from 'utils/utils.jsx';
+import {FileTypes} from 'utils/constants.jsx';
+import {
+ canDownloadFiles,
+ trimFilename
+} from 'utils/file_utils';
+import {
+ fileSizeToString,
+ getFileType,
+ loadImage
+} from 'utils/utils.jsx';
import FilenameOverlay from 'components/file_attachment/filename_overlay.jsx';
+import FileThumbnail from 'components/file_attachment/file_thumbnail.jsx';
import DownloadIcon from 'components/svg/download_icon';
export default class FileAttachment extends React.PureComponent {
@@ -40,7 +48,7 @@ export default class FileAttachment extends React.PureComponent {
super(props);
this.state = {
- loaded: Utils.getFileType(props.fileInfo.extension) !== FileTypes.IMAGE
+ loaded: getFileType(props.fileInfo.extension) !== FileTypes.IMAGE
};
}
@@ -53,7 +61,7 @@ export default class FileAttachment extends React.PureComponent {
const extension = nextProps.fileInfo.extension;
this.setState({
- loaded: Utils.getFileType(extension) !== FileTypes.IMAGE && extension !== FileTypes.SVG
+ loaded: getFileType(extension) !== FileTypes.IMAGE && extension !== FileTypes.SVG
});
}
}
@@ -66,14 +74,14 @@ export default class FileAttachment extends React.PureComponent {
loadFiles = () => {
const fileInfo = this.props.fileInfo;
- const fileType = Utils.getFileType(fileInfo.extension);
+ const fileType = getFileType(fileInfo.extension);
if (fileType === FileTypes.IMAGE) {
const thumbnailUrl = getFileThumbnailUrl(fileInfo.id);
- Utils.loadImage(thumbnailUrl, this.handleImageLoaded);
+ loadImage(thumbnailUrl, this.handleImageLoaded);
} else if (fileInfo.extension === FileTypes.SVG) {
- Utils.loadImage(getFileUrl(fileInfo.id), this.handleImageLoaded);
+ loadImage(getFileUrl(fileInfo.id), this.handleImageLoaded);
}
}
@@ -89,67 +97,14 @@ export default class FileAttachment extends React.PureComponent {
}
render() {
- const fileInfo = this.props.fileInfo;
- const fileName = fileInfo.name;
- const fileUrl = getFileUrl(fileInfo.id);
-
- let thumbnail;
- if (this.state.loaded) {
- const type = Utils.getFileType(fileInfo.extension);
-
- if (type === FileTypes.IMAGE) {
- let className = 'post-image';
-
- if (fileInfo.width < Constants.THUMBNAIL_WIDTH && fileInfo.height < Constants.THUMBNAIL_HEIGHT) {
- className += ' small';
- } else {
- className += ' normal';
- }
-
- let thumbnailUrl = getFileThumbnailUrl(fileInfo.id);
- if (Utils.isGIFImage(fileInfo.extension) && !fileInfo.has_preview_image) {
- thumbnailUrl = getFileUrl(fileInfo.id);
- }
-
- thumbnail = (
-
- );
- } else if (fileInfo.extension === FileTypes.SVG) {
- thumbnail = (
-
- );
- } else {
- thumbnail = ;
- }
- } else {
- thumbnail = ;
- }
+ const {
+ compactDisplay,
+ fileInfo,
+ index
+ } = this.props;
- const canDownloadFiles = FileUtils.canDownloadFiles();
-
- let downloadButton = null;
- if (canDownloadFiles) {
- downloadButton = (
-
-
-
- );
- }
+ const trimmedFilename = trimFilename(fileInfo.name);
+ const canDownload = canDownloadFiles();
return (
@@ -158,7 +113,11 @@ export default class FileAttachment extends React.PureComponent {
href='#'
onClick={this.onAttachmentClick}
>
- {thumbnail}
+ {this.state.loaded ? (
+
+ ) : (
+
+ )}
-
+
+ {trimmedFilename}
+
{fileInfo.extension.toUpperCase()}
- {Utils.fileSizeToString(fileInfo.size)}
+ {fileSizeToString(fileInfo.size)}
- {downloadButton}
+ {canDownload &&
+
+
+
+ }
);
diff --git a/components/file_attachment/file_thumbnail.jsx b/components/file_attachment/file_thumbnail.jsx
new file mode 100644
index 000000000000..527c1648b652
--- /dev/null
+++ b/components/file_attachment/file_thumbnail.jsx
@@ -0,0 +1,67 @@
+// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import {getFileThumbnailUrl, getFileUrl} from 'mattermost-redux/utils/file_utils';
+
+import Constants, {FileTypes} from 'utils/constants.jsx';
+import {
+ getFileType,
+ getIconClassName,
+ isGIFImage
+} from 'utils/utils.jsx';
+
+export default class FileThumbnail extends React.PureComponent {
+
+ static propTypes = {
+
+ /*
+ * File detailed information
+ */
+ fileInfo: PropTypes.object.isRequired
+ }
+
+ render() {
+ const {fileInfo} = this.props;
+ const type = getFileType(fileInfo.extension);
+
+ let thumbnail;
+ if (type === FileTypes.IMAGE) {
+ let className = 'post-image';
+
+ if (fileInfo.width < Constants.THUMBNAIL_WIDTH && fileInfo.height < Constants.THUMBNAIL_HEIGHT) {
+ className += ' small';
+ } else {
+ className += ' normal';
+ }
+
+ let thumbnailUrl = getFileThumbnailUrl(fileInfo.id);
+ if (isGIFImage(fileInfo.extension) && !fileInfo.has_preview_image) {
+ thumbnailUrl = getFileUrl(fileInfo.id);
+ }
+
+ return (
+
+ );
+ } else if (fileInfo.extension === FileTypes.SVG) {
+ thumbnail = (
+
+ );
+ } else {
+ thumbnail = ;
+ }
+
+ return thumbnail;
+ }
+}
diff --git a/components/file_attachment/filename_overlay.jsx b/components/file_attachment/filename_overlay.jsx
index 4c8166fb5e06..b0e9994f34f9 100644
--- a/components/file_attachment/filename_overlay.jsx
+++ b/components/file_attachment/filename_overlay.jsx
@@ -7,7 +7,8 @@ import {OverlayTrigger, Tooltip} from 'react-bootstrap';
import {getFileUrl} from 'mattermost-redux/utils/file_utils';
import AttachmentIcon from 'components/svg/attachment_icon';
-import * as Utils from 'utils/utils.jsx';
+import {trimFilename} from 'utils/file_utils';
+import {localizeMessage} from 'utils/utils.jsx';
export default class FilenameOverlay extends React.PureComponent {
static propTypes = {
@@ -35,7 +36,17 @@ export default class FilenameOverlay extends React.PureComponent {
/*
* If it should display link to download on file name
*/
- canDownload: PropTypes.bool
+ canDownload: PropTypes.bool,
+
+ /**
+ * Optional children like download icon
+ */
+ children: PropTypes.element,
+
+ /**
+ * Optional class like for icon
+ */
+ iconClass: PropTypes.string
};
onAttachmentClick = (e) => {
@@ -44,19 +55,20 @@ export default class FilenameOverlay extends React.PureComponent {
}
render() {
- const fileInfo = this.props.fileInfo;
+ const {
+ canDownload,
+ children,
+ compactDisplay,
+ fileInfo,
+ iconClass
+ } = this.props;
+
const fileName = fileInfo.name;
+ const trimmedFilename = trimFilename(fileName);
const fileUrl = getFileUrl(fileInfo.id);
- let trimmedFilename;
- if (fileName.length > 35) {
- trimmedFilename = fileName.substring(0, Math.min(35, fileName.length)) + '...';
- } else {
- trimmedFilename = fileName;
- }
-
let filenameOverlay;
- if (this.props.compactDisplay) {
+ if (compactDisplay) {
filenameOverlay = (
);
- } else if (this.props.canDownload) {
+ } else if (canDownload) {
filenameOverlay = (
- {Utils.localizeMessage('file_attachment.download', 'Download') + ' "' + fileName + '"'}}
+
-
+ {localizeMessage('file_attachment.download', 'Download')}
+
+ }
>
- {trimmedFilename}
-
-
+ {children || trimmedFilename}
+
+
);
} else {
filenameOverlay = (
diff --git a/sass/components/_files.scss b/sass/components/_files.scss
index f7d303bd2e2e..bd418fc6187b 100644
--- a/sass/components/_files.scss
+++ b/sass/components/_files.scss
@@ -357,7 +357,7 @@
height: 100%;
justify-content: center;
line-height: 0;
- padding-right: 10px;
+ padding: 0 5px;
position: absolute;
right: 0;
text-align: center;
diff --git a/tests/components/__snapshots__/file_attachment.test.jsx.snap b/tests/components/__snapshots__/file_attachment.test.jsx.snap
index e8411c64071e..44c66745292f 100644
--- a/tests/components/__snapshots__/file_attachment.test.jsx.snap
+++ b/tests/components/__snapshots__/file_attachment.test.jsx.snap
@@ -9,12 +9,15 @@ exports[`component/FileAttachment should match snapshot, after change from file
href="#"
onClick={[Function]}
>
-
@@ -29,21 +32,11 @@ exports[`component/FileAttachment should match snapshot, after change from file
-
+
+ test.png
+
@@ -56,15 +49,23 @@ exports[`component/FileAttachment should match snapshot, after change from file
-
-
+
`;
@@ -78,8 +79,15 @@ exports[`component/FileAttachment should match snapshot, file with long name 1`]
href="#"
onClick={[Function]}
>
-
-
+
+ a-quite-long-filename-to-test-the-f...
+
@@ -117,15 +117,21 @@ exports[`component/FileAttachment should match snapshot, file with long name 1`]
-
-
+
`;
@@ -139,8 +145,15 @@ exports[`component/FileAttachment should match snapshot, regular file 1`] = `
href="#"
onClick={[Function]}
>
-
-
+
+ test.pdf
+
@@ -178,15 +183,21 @@ exports[`component/FileAttachment should match snapshot, regular file 1`] = `
-
-
+
`;
@@ -200,12 +211,15 @@ exports[`component/FileAttachment should match snapshot, regular image 1`] = `
href="#"
onClick={[Function]}
>
-
@@ -220,21 +234,11 @@ exports[`component/FileAttachment should match snapshot, regular image 1`] = `
-
+
+ test.png
+
@@ -247,15 +251,23 @@ exports[`component/FileAttachment should match snapshot, regular image 1`] = `
-
-
+
`;
@@ -269,12 +281,15 @@ exports[`component/FileAttachment should match snapshot, small image 1`] = `
href="#"
onClick={[Function]}
>
-
@@ -289,21 +304,11 @@ exports[`component/FileAttachment should match snapshot, small image 1`] = `
-
+
+ test.png
+
@@ -316,15 +321,23 @@ exports[`component/FileAttachment should match snapshot, small image 1`] = `
-
-
+
`;
@@ -338,9 +351,17 @@ exports[`component/FileAttachment should match snapshot, svg image 1`] = `
href="#"
onClick={[Function]}
>
-
-
+
+ test.svg
+
@@ -380,15 +391,23 @@ exports[`component/FileAttachment should match snapshot, svg image 1`] = `
-
-
+
`;
@@ -416,19 +435,11 @@ exports[`component/FileAttachment should match snapshot, when file is not loaded
-
+
+ test.pdf
+
@@ -441,15 +452,21 @@ exports[`component/FileAttachment should match snapshot, when file is not loaded
-
-
+
`;
@@ -463,8 +480,15 @@ exports[`component/FileAttachment should match snapshot, with compact display 1`
href="#"
onClick={[Function]}
>
-
-
+
+ test.pdf
+
@@ -503,15 +518,22 @@ exports[`component/FileAttachment should match snapshot, with compact display 1`
-
-
+
`;
@@ -525,8 +547,15 @@ exports[`component/FileAttachment should match snapshot, without compact display
href="#"
onClick={[Function]}
>
-
-
+
+ test.pdf
+
diff --git a/tests/components/file_attachment/__snapshots__/file_thumbnail.test.jsx.snap b/tests/components/file_attachment/__snapshots__/file_thumbnail.test.jsx.snap
new file mode 100644
index 000000000000..6b5eb87d7648
--- /dev/null
+++ b/tests/components/file_attachment/__snapshots__/file_thumbnail.test.jsx.snap
@@ -0,0 +1,38 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`components/file_attachment/FileThumbnail should match snapshot, normal size image 1`] = `
+
+`;
+
+exports[`components/file_attachment/FileThumbnail should match snapshot, pdf 1`] = `
+
+`;
+
+exports[`components/file_attachment/FileThumbnail should match snapshot, small size image 1`] = `
+
+`;
+
+exports[`components/file_attachment/FileThumbnail should match snapshot, svg 1`] = `
+
+`;
diff --git a/tests/components/file_attachment/file_thumbnail.test.jsx b/tests/components/file_attachment/file_thumbnail.test.jsx
new file mode 100644
index 000000000000..659bb5ff8355
--- /dev/null
+++ b/tests/components/file_attachment/file_thumbnail.test.jsx
@@ -0,0 +1,52 @@
+// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+import {shallow} from 'enzyme';
+
+import FileThumbnail from 'components/file_attachment/file_thumbnail.jsx';
+
+describe('components/file_attachment/FileThumbnail', () => {
+ const fileInfo = {
+ id: 'thumbnail_id',
+ extension: 'jpg',
+ width: 100,
+ height: 80,
+ has_preview_image: true
+ };
+
+ test('should match snapshot, small size image', () => {
+ const wrapper = shallow(
+
+ );
+
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ test('should match snapshot, normal size image', () => {
+ const newFileInfo = {...fileInfo, height: 150, width: 150};
+ const wrapper = shallow(
+
+ );
+
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ test('should match snapshot, svg', () => {
+ const newFileInfo = {...fileInfo, extension: 'svg'};
+ const wrapper = shallow(
+
+ );
+
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ test('should match snapshot, pdf', () => {
+ const newFileInfo = {...fileInfo, extension: 'pdf'};
+ const wrapper = shallow(
+
+ );
+
+ expect(wrapper).toMatchSnapshot();
+ });
+});
diff --git a/tests/utils/file_utils.test.jsx b/tests/utils/file_utils.test.jsx
new file mode 100644
index 000000000000..ff282ed39927
--- /dev/null
+++ b/tests/utils/file_utils.test.jsx
@@ -0,0 +1,22 @@
+// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import assert from 'assert';
+
+import {trimFilename} from 'utils/file_utils.jsx';
+
+describe('FileUtils.trimFilename', function() {
+ it('trimFilename', function() {
+ assert.equal(
+ trimFilename('abcdefghijklmnopqrstuvwxyz'),
+ 'abcdefghijklmnopqrstuvwxyz',
+ 'should return same filename'
+ );
+
+ assert.equal(
+ trimFilename('abcdefghijklmnopqrstuvwxyz0123456789'),
+ 'abcdefghijklmnopqrstuvwxyz012345678...',
+ 'should return trimmed filename'
+ );
+ });
+});
diff --git a/utils/constants.jsx b/utils/constants.jsx
index f1437885c1fb..7546097f144b 100644
--- a/utils/constants.jsx
+++ b/utils/constants.jsx
@@ -540,6 +540,7 @@ export const Constants = {
},
MAX_DISPLAY_FILES: 5,
MAX_UPLOAD_FILES: 5,
+ MAX_FILENAME_LENGTH: 35,
THUMBNAIL_WIDTH: 128,
THUMBNAIL_HEIGHT: 100,
PROFILE_WIDTH: 128,
diff --git a/utils/file_utils.jsx b/utils/file_utils.jsx
index 42feb11be260..ee0b196ef1ff 100644
--- a/utils/file_utils.jsx
+++ b/utils/file_utils.jsx
@@ -1,6 +1,7 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import Constants from 'utils/constants.jsx';
import * as UserAgent from 'utils/user_agent';
export function canUploadFiles() {
@@ -22,3 +23,12 @@ export function canDownloadFiles() {
return true;
}
+
+export function trimFilename(filename) {
+ let trimmedFilename = filename;
+ if (filename.length > Constants.MAX_FILENAME_LENGTH) {
+ trimmedFilename = filename.substring(0, Math.min(Constants.MAX_FILENAME_LENGTH, filename.length)) + '...';
+ }
+
+ return trimmedFilename;
+}