summaryrefslogtreecommitdiffstats
path: root/webapp/components
diff options
context:
space:
mode:
authorHarrison Healey <harrisonmhealey@gmail.com>2016-09-30 11:06:30 -0400
committerGitHub <noreply@github.com>2016-09-30 11:06:30 -0400
commit8a0e649f989a824bb3bbfd1900a5b8e5383b47e1 (patch)
tree4b424929fe13ebec438d2f41a2729e37e5160720 /webapp/components
parenta2deeed597dea15d9b7ca237be71988469f58cdd (diff)
downloadchat-8a0e649f989a824bb3bbfd1900a5b8e5383b47e1.tar.gz
chat-8a0e649f989a824bb3bbfd1900a5b8e5383b47e1.tar.bz2
chat-8a0e649f989a824bb3bbfd1900a5b8e5383b47e1.zip
PLT-3105 Files table migration (#4068)
* Implemented initial changes for files table * Removed *_benchmark_test.go files * Re-implemented GetPublicFile and added support for old path * Localization for files table * Moved file system code into utils package * Finished server-side changes and added initial upgrade script * Added getPostFiles api * Re-add Extension and HasPreviewImage fields to FileInfo * Removed unused translation * Fixed merge conflicts left over after permissions changes * Forced FileInfo.extension to be lower case * Changed FileUploadResponse to contain the FileInfos instead of FileIds * Fixed permissions on getFile* calls * Fixed notifications for file uploads * Added initial version of client code for files changes * Permanently added FileIds field to Post object and removed Post.HasFiles * Updated PostStore.Update to be usable in more circumstances * Re-added Filenames field and switched file migration to be entirely lazy-loaded * Increased max listener count for FileStore * Removed unused fileInfoCache * Moved file system code back into api * Removed duplicate test case * Fixed unit test running on ports other than 8065 * Renamed HasPermissionToPostContext to HasPermissionToChannelByPostContext * Refactored handleImages to make it more easily understandable * Renamed getPostFiles to getFileInfosForPost * Re-added pre-FileIds posts to analytics * Changed files to be saved as their ids as opposed to id/filename.ext * Renamed FileInfo.UserId to FileInfo.CreatorId * Fixed detection of language in CodePreview * Fixed switching between threads in the RHS not loading new files * Add serverside protection against a rare bug where the client sends the same file twice for a single post * Refactored the important parts of uploadFile api call into a function that can be called without a web context
Diffstat (limited to 'webapp/components')
-rw-r--r--webapp/components/audio_video_preview.jsx12
-rw-r--r--webapp/components/code_preview.jsx18
-rw-r--r--webapp/components/create_comment.jsx36
-rw-r--r--webapp/components/create_post.jsx47
-rw-r--r--webapp/components/file_attachment.jsx251
-rw-r--r--webapp/components/file_attachment_list.jsx55
-rw-r--r--webapp/components/file_attachment_list_container.jsx90
-rw-r--r--webapp/components/file_info_preview.jsx88
-rw-r--r--webapp/components/file_preview.jsx83
-rw-r--r--webapp/components/file_upload.jsx9
-rw-r--r--webapp/components/get_public_link_modal.jsx6
-rw-r--r--webapp/components/pdf_preview.jsx24
-rw-r--r--webapp/components/post_view/components/commented_on_files_message_container.jsx88
-rw-r--r--webapp/components/post_view/components/post_body.jsx31
-rw-r--r--webapp/components/rhs_comment.jsx12
-rw-r--r--webapp/components/rhs_root_post.jsx12
-rw-r--r--webapp/components/view_image.jsx140
17 files changed, 489 insertions, 513 deletions
diff --git a/webapp/components/audio_video_preview.jsx b/webapp/components/audio_video_preview.jsx
index dd2e910b3..4956900a9 100644
--- a/webapp/components/audio_video_preview.jsx
+++ b/webapp/components/audio_video_preview.jsx
@@ -76,10 +76,8 @@ export default class AudioVideoPreview extends React.Component {
if (!this.state.canPlay) {
return (
<FileInfoPreview
- filename={this.props.filename}
- fileUrl={this.props.fileUrl}
fileInfo={this.props.fileInfo}
- formatMessage={this.props.formatMessage}
+ fileUrl={this.props.fileUrl}
/>
);
}
@@ -94,7 +92,7 @@ export default class AudioVideoPreview extends React.Component {
// add a key to the video to prevent React from using an old video source while a new one is loading
return (
<video
- key={this.props.filename}
+ key={this.props.fileInfo.id}
ref='video'
style={{maxHeight: this.props.maxHeight}}
data-setup='{}'
@@ -112,9 +110,7 @@ export default class AudioVideoPreview extends React.Component {
}
AudioVideoPreview.propTypes = {
- filename: React.PropTypes.string.isRequired,
- fileUrl: React.PropTypes.string.isRequired,
fileInfo: React.PropTypes.object.isRequired,
- maxHeight: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]).isRequired,
- formatMessage: React.PropTypes.func.isRequired
+ fileUrl: React.PropTypes.string.isRequired,
+ maxHeight: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]).isRequired
};
diff --git a/webapp/components/code_preview.jsx b/webapp/components/code_preview.jsx
index 6625f45f4..852b26e25 100644
--- a/webapp/components/code_preview.jsx
+++ b/webapp/components/code_preview.jsx
@@ -38,7 +38,7 @@ export default class CodePreview extends React.Component {
}
updateStateFromProps(props) {
- var usedLanguage = SyntaxHighlighting.getLanguageFromFilename(props.filename);
+ const usedLanguage = SyntaxHighlighting.getLanguageFromFileExtension(props.fileInfo.extension);
if (!usedLanguage || props.fileInfo.size > Constants.CODE_PREVIEW_MAX_FILE_SIZE) {
this.setState({code: '', lang: '', loading: false, success: false});
@@ -64,8 +64,8 @@ export default class CodePreview extends React.Component {
this.setState({loading: false, success: false});
}
- static support(filename) {
- return Boolean(SyntaxHighlighting.getLanguageFromFilename(filename));
+ static supports(fileInfo) {
+ return Boolean(SyntaxHighlighting.getLanguageFromFileExtension(fileInfo.extension));
}
render() {
@@ -83,10 +83,8 @@ export default class CodePreview extends React.Component {
if (!this.state.success) {
return (
<FileInfoPreview
- filename={this.props.filename}
- fileUrl={this.props.fileUrl}
fileInfo={this.props.fileInfo}
- formatMessage={this.props.formatMessage}
+ fileUrl={this.props.fileUrl}
/>
);
}
@@ -106,12 +104,10 @@ export default class CodePreview extends React.Component {
const highlighted = SyntaxHighlighting.highlight(this.state.lang, this.state.code);
- const fileName = this.props.filename.substring(this.props.filename.lastIndexOf('/') + 1, this.props.filename.length);
-
return (
<div className='post-code'>
<span className='post-code__language'>
- {`${fileName} - ${language}`}
+ {`${this.props.fileInfo.name} - ${language}`}
</span>
<code className='hljs'>
<table>
@@ -129,8 +125,6 @@ export default class CodePreview extends React.Component {
}
CodePreview.propTypes = {
- filename: React.PropTypes.string.isRequired,
- fileUrl: React.PropTypes.string.isRequired,
fileInfo: React.PropTypes.object.isRequired,
- formatMessage: React.PropTypes.func.isRequired
+ fileUrl: React.PropTypes.string.isRequired
};
diff --git a/webapp/components/create_comment.jsx b/webapp/components/create_comment.jsx
index 2f0698510..133c2e6d2 100644
--- a/webapp/components/create_comment.jsx
+++ b/webapp/components/create_comment.jsx
@@ -55,7 +55,7 @@ export default class CreateComment extends React.Component {
this.state = {
messageText: draft.message,
uploadsInProgress: draft.uploadsInProgress,
- previews: draft.previews,
+ fileInfos: draft.fileInfos,
submitting: false,
ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter'),
showPostDeletedModal: false
@@ -99,10 +99,10 @@ export default class CreateComment extends React.Component {
}
const post = {};
- post.filenames = [];
+ post.file_ids = [];
post.message = this.state.messageText;
- if (post.message.trim().length === 0 && this.state.previews.length === 0) {
+ if (post.message.trim().length === 0 && this.state.fileInfos.length === 0) {
return;
}
@@ -126,7 +126,7 @@ export default class CreateComment extends React.Component {
post.channel_id = this.props.channelId;
post.root_id = this.props.rootId;
post.parent_id = this.props.rootId;
- post.filenames = this.state.previews;
+ post.file_ids = this.state.fileInfos.map((info) => info.id);
const time = Utils.getTimestamp();
post.pending_post_id = `${userId}:${time}`;
post.user_id = userId;
@@ -163,7 +163,7 @@ export default class CreateComment extends React.Component {
messageText: '',
submitting: false,
postError: null,
- previews: [],
+ fileInfos: [],
serverError: null
});
}
@@ -245,7 +245,7 @@ export default class CreateComment extends React.Component {
this.focusTextbox();
}
- handleFileUploadComplete(filenames, clientIds) {
+ handleFileUploadComplete(fileInfos, clientIds) {
const draft = PostStore.getCommentDraft(this.props.rootId);
// remove each finished file from uploads
@@ -257,10 +257,10 @@ export default class CreateComment extends React.Component {
}
}
- draft.previews = draft.previews.concat(filenames);
+ draft.fileInfos = draft.fileInfos.concat(fileInfos);
PostStore.storeCommentDraft(this.props.rootId, draft);
- this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews});
+ this.setState({uploadsInProgress: draft.uploadsInProgress, fileInfos: draft.fileInfos});
}
handleUploadError(err, clientId) {
@@ -281,11 +281,11 @@ export default class CreateComment extends React.Component {
}
removePreview(id) {
- const previews = this.state.previews;
+ const fileInfos = this.state.fileInfos;
const uploadsInProgress = this.state.uploadsInProgress;
- // id can either be the path of an uploaded file or the client id of an in progress upload
- let index = previews.indexOf(id);
+ // id can either be the id of an uploaded file or the client id of an in progress upload
+ let index = fileInfos.findIndex((info) => info.id === id);
if (index === -1) {
index = uploadsInProgress.indexOf(id);
@@ -294,26 +294,26 @@ export default class CreateComment extends React.Component {
this.refs.fileUpload.getWrappedInstance().cancelUpload(id);
}
} else {
- previews.splice(index, 1);
+ fileInfos.splice(index, 1);
}
const draft = PostStore.getCommentDraft(this.props.rootId);
- draft.previews = previews;
+ draft.fileInfos = fileInfos;
draft.uploadsInProgress = uploadsInProgress;
PostStore.storeCommentDraft(this.props.rootId, draft);
- this.setState({previews, uploadsInProgress});
+ this.setState({fileInfos, uploadsInProgress});
}
componentWillReceiveProps(newProps) {
if (newProps.rootId !== this.props.rootId) {
const draft = PostStore.getCommentDraft(newProps.rootId);
- this.setState({messageText: draft.message, uploadsInProgress: draft.uploadsInProgress, previews: draft.previews});
+ this.setState({messageText: draft.message, uploadsInProgress: draft.uploadsInProgress, fileInfos: draft.fileInfos});
}
}
getFileCount() {
- return this.state.previews.length + this.state.uploadsInProgress.length;
+ return this.state.fileInfos.length + this.state.uploadsInProgress.length;
}
focusTextbox() {
@@ -350,10 +350,10 @@ export default class CreateComment extends React.Component {
}
let preview = null;
- if (this.state.previews.length > 0 || this.state.uploadsInProgress.length > 0) {
+ if (this.state.fileInfos.length > 0 || this.state.uploadsInProgress.length > 0) {
preview = (
<FilePreview
- files={this.state.previews}
+ fileInfos={this.state.fileInfos}
onRemove={this.removePreview}
uploadsInProgress={this.state.uploadsInProgress}
/>
diff --git a/webapp/components/create_post.jsx b/webapp/components/create_post.jsx
index bfacd0644..d3417e419 100644
--- a/webapp/components/create_post.jsx
+++ b/webapp/components/create_post.jsx
@@ -67,7 +67,7 @@ export default class CreatePost extends React.Component {
channelId: ChannelStore.getCurrentId(),
messageText: draft.messageText,
uploadsInProgress: draft.uploadsInProgress,
- previews: draft.previews,
+ fileInfos: draft.fileInfos,
submitting: false,
initialText: draft.messageText,
ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter'),
@@ -79,14 +79,14 @@ export default class CreatePost extends React.Component {
getCurrentDraft() {
const draft = PostStore.getCurrentDraft();
- const safeDraft = {previews: [], messageText: '', uploadsInProgress: []};
+ const safeDraft = {fileInfos: [], messageText: '', uploadsInProgress: []};
if (draft) {
if (draft.message) {
safeDraft.messageText = draft.message;
}
- if (draft.previews) {
- safeDraft.previews = draft.previews;
+ if (draft.fileInfos) {
+ safeDraft.fileInfos = draft.fileInfos;
}
if (draft.uploadsInProgress) {
safeDraft.uploadsInProgress = draft.uploadsInProgress;
@@ -104,10 +104,10 @@ export default class CreatePost extends React.Component {
}
const post = {};
- post.filenames = [];
+ post.file_ids = [];
post.message = this.state.messageText;
- if (post.message.trim().length === 0 && this.state.previews.length === 0) {
+ if (post.message.trim().length === 0 && this.state.fileInfos.length === 0) {
return;
}
@@ -122,7 +122,7 @@ export default class CreatePost extends React.Component {
if (post.message.indexOf('/') === 0) {
PostStore.storeDraft(this.state.channelId, null);
- this.setState({messageText: '', postError: null, previews: []});
+ this.setState({messageText: '', postError: null, fileInfos: []});
ChannelActions.executeCommand(
this.state.channelId,
@@ -153,7 +153,7 @@ export default class CreatePost extends React.Component {
sendMessage(post) {
post.channel_id = this.state.channelId;
- post.filenames = this.state.previews;
+ post.file_ids = this.state.fileInfos.map((info) => info.id);
const time = Utils.getTimestamp();
const userId = UserStore.getCurrentId();
@@ -163,7 +163,7 @@ export default class CreatePost extends React.Component {
post.parent_id = this.state.parentId;
GlobalActions.emitUserPostedEvent(post);
- this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
+ this.setState({messageText: '', submitting: false, postError: null, fileInfos: [], serverError: null});
Client.createPost(post,
(data) => {
@@ -236,7 +236,7 @@ export default class CreatePost extends React.Component {
this.focusTextbox();
}
- handleFileUploadComplete(filenames, clientIds, channelId) {
+ handleFileUploadComplete(fileInfos, clientIds, channelId) {
const draft = PostStore.getDraft(channelId);
// remove each finished file from uploads
@@ -248,11 +248,11 @@ export default class CreatePost extends React.Component {
}
}
- draft.previews = draft.previews.concat(filenames);
+ draft.fileInfos = draft.fileInfos.concat(fileInfos);
PostStore.storeDraft(channelId, draft);
if (channelId === this.state.channelId) {
- this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews});
+ this.setState({uploadsInProgress: draft.uploadsInProgress, fileInfos: draft.fileInfos});
}
}
@@ -282,11 +282,11 @@ export default class CreatePost extends React.Component {
}
removePreview(id) {
- const previews = Object.assign([], this.state.previews);
+ const fileInfos = Object.assign([], this.state.fileInfos);
const uploadsInProgress = this.state.uploadsInProgress;
- // id can either be the path of an uploaded file or the client id of an in progress upload
- let index = previews.indexOf(id);
+ // id can either be the id of an uploaded file or the client id of an in progress upload
+ let index = fileInfos.findIndex((info) => info.id === id);
if (index === -1) {
index = uploadsInProgress.indexOf(id);
@@ -295,15 +295,15 @@ export default class CreatePost extends React.Component {
this.refs.fileUpload.getWrappedInstance().cancelUpload(id);
}
} else {
- previews.splice(index, 1);
+ fileInfos.splice(index, 1);
}
const draft = PostStore.getCurrentDraft();
- draft.previews = previews;
+ draft.fileInfos = fileInfos;
draft.uploadsInProgress = uploadsInProgress;
PostStore.storeCurrentDraft(draft);
- this.setState({previews, uploadsInProgress});
+ this.setState({fileInfos, uploadsInProgress});
}
componentWillMount() {
@@ -336,6 +336,7 @@ export default class CreatePost extends React.Component {
PreferenceStore.removeChangeListener(this.onPreferenceChange);
document.removeEventListener('keydown', this.showShortcuts);
}
+
showShortcuts(e) {
if ((e.ctrlKey || e.metaKey) && e.keyCode === Constants.KeyCodes.FORWARD_SLASH) {
e.preventDefault();
@@ -359,7 +360,7 @@ export default class CreatePost extends React.Component {
if (this.state.channelId !== channelId) {
const draft = this.getCurrentDraft();
- this.setState({channelId, messageText: draft.messageText, initialText: draft.messageText, submitting: false, serverError: null, postError: null, previews: draft.previews, uploadsInProgress: draft.uploadsInProgress});
+ this.setState({channelId, messageText: draft.messageText, initialText: draft.messageText, submitting: false, serverError: null, postError: null, fileInfos: draft.fileInfos, uploadsInProgress: draft.uploadsInProgress});
}
}
@@ -374,11 +375,11 @@ export default class CreatePost extends React.Component {
getFileCount(channelId) {
if (channelId === this.state.channelId) {
- return this.state.previews.length + this.state.uploadsInProgress.length;
+ return this.state.fileInfos.length + this.state.uploadsInProgress.length;
}
const draft = PostStore.getDraft(channelId);
- return draft.previews.length + draft.uploadsInProgress.length;
+ return draft.fileInfos.length + draft.uploadsInProgress.length;
}
handleKeyDown(e) {
@@ -474,10 +475,10 @@ export default class CreatePost extends React.Component {
}
let preview = null;
- if (this.state.previews.length > 0 || this.state.uploadsInProgress.length > 0) {
+ if (this.state.fileInfos.length > 0 || this.state.uploadsInProgress.length > 0) {
preview = (
<FilePreview
- files={this.state.previews}
+ fileInfos={this.state.fileInfos}
onRemove={this.removePreview}
uploadsInProgress={this.state.uploadsInProgress}
/>
diff --git a/webapp/components/file_attachment.jsx b/webapp/components/file_attachment.jsx
index cba9d8288..23d8d2446 100644
--- a/webapp/components/file_attachment.jsx
+++ b/webapp/components/file_attachment.jsx
@@ -1,204 +1,111 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import $ from 'jquery';
-import ReactDOM from 'react-dom';
-import * as utils from 'utils/utils.jsx';
-import Client from 'client/web_client.jsx';
import Constants from 'utils/constants.jsx';
+import FileStore from 'stores/file_store.jsx';
+import * as Utils from 'utils/utils.jsx';
-import {intlShape, injectIntl, defineMessages} from 'react-intl';
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
-const holders = defineMessages({
- download: {
- id: 'file_attachment.download',
- defaultMessage: 'Download'
- }
-});
-
import React from 'react';
-class FileAttachment extends React.Component {
+export default class FileAttachment extends React.Component {
constructor(props) {
super(props);
this.loadFiles = this.loadFiles.bind(this);
- this.addBackgroundImage = this.addBackgroundImage.bind(this);
this.onAttachmentClick = this.onAttachmentClick.bind(this);
- this.canSetState = false;
- this.state = {fileSize: -1};
+ this.state = {
+ loaded: Utils.getFileType(props.fileInfo.extension) !== 'image'
+ };
}
+
componentDidMount() {
this.loadFiles();
}
- componentDidUpdate(prevProps) {
- if (this.props.filename !== prevProps.filename) {
- this.loadFiles();
- }
- }
- loadFiles() {
- this.canSetState = true;
-
- var filename = this.props.filename;
-
- if (filename) {
- var fileInfo = this.getFileInfoFromName(filename);
- var type = utils.getFileType(fileInfo.ext);
-
- if (type === 'image') {
- var self = this; // Need this reference since we use the given "this"
- $('<img/>').attr('src', fileInfo.path + '_thumb.jpg').on('load', (function loadWrapper(path, name) {
- return function loader() {
- $(this).remove();
- if (name in self.refs) {
- var imgDiv = ReactDOM.findDOMNode(self.refs[name]);
-
- $(imgDiv).removeClass('post-image__load');
- $(imgDiv).addClass('post-image');
-
- var width = this.width || $(this).width();
- var height = this.height || $(this).height();
-
- if (width < Constants.THUMBNAIL_WIDTH &&
- height < Constants.THUMBNAIL_HEIGHT) {
- $(imgDiv).addClass('small');
- } else {
- $(imgDiv).addClass('normal');
- }
- self.addBackgroundImage(name, path);
- }
- };
- }(fileInfo.path, filename)));
- }
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.fileInfo.id !== this.props.fileInfo.id) {
+ this.setState({
+ loaded: Utils.getFileType(nextProps.fileInfo.extension) !== 'image'
+ });
}
}
- componentWillUnmount() {
- // keep track of when this component is mounted so that we can asynchronously change state without worrying about whether or not we're mounted
- this.canSetState = false;
- }
- shouldComponentUpdate(nextProps, nextState) {
- if (!utils.areObjectsEqual(nextProps, this.props)) {
- return true;
- }
-
- // the only time this object should update is when it receives an updated file size which we can usually handle without re-rendering
- if (nextState.fileSize !== this.state.fileSize) {
- if (this.refs.fileSize) {
- // update the UI element to display the file size without re-rendering the whole component
- ReactDOM.findDOMNode(this.refs.fileSize).innerHTML = utils.fileSizeToString(nextState.fileSize);
- return false;
- }
-
- // we can't find the element that should hold the file size so we must not have rendered yet
- return true;
+ componentDidUpdate(prevProps) {
+ if (!this.state.loaded && this.props.fileInfo.id !== prevProps.fileInfo.id) {
+ this.loadFiles();
}
-
- return true;
- }
- getFileInfoFromName(name) {
- var fileInfo = utils.splitFileLocation(name);
-
- fileInfo.path = Client.getFilesRoute() + '/get' + fileInfo.path;
-
- return fileInfo;
}
- addBackgroundImage(name, path) {
- var fileUrl = path;
- if (name in this.refs) {
- if (!path) {
- fileUrl = this.getFileInfoFromName(name).path;
- }
+ loadFiles() {
+ const fileInfo = this.props.fileInfo;
+ const fileType = Utils.getFileType(fileInfo.extension);
- var imgDiv = ReactDOM.findDOMNode(this.refs[name]);
- var re1 = new RegExp(' ', 'g');
- var re2 = new RegExp('\\(', 'g');
- var re3 = new RegExp('\\)', 'g');
- var url = fileUrl.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29');
+ if (fileType === 'image') {
+ const thumbnailUrl = FileStore.getFileThumbnailUrl(fileInfo.id);
- $(imgDiv).css('background-image', 'url(' + url + '_thumb.jpg)');
- }
- }
- removeBackgroundImage(name) {
- if (name in this.refs) {
- $(ReactDOM.findDOMNode(this.refs[name])).css('background-image', 'initial');
+ const img = new Image();
+ img.onload = () => {
+ this.setState({loaded: true});
+ };
+ img.load(thumbnailUrl);
}
}
+
onAttachmentClick(e) {
e.preventDefault();
this.props.handleImageClick(this.props.index);
}
+
render() {
- var filename = this.props.filename;
+ const fileInfo = this.props.fileInfo;
+ const fileName = fileInfo.name;
+ const fileUrl = FileStore.getFileUrl(fileInfo.id);
- var fileInfo = utils.splitFileLocation(filename);
- var fileUrl = utils.getFileUrl(filename);
- var type = utils.getFileType(fileInfo.ext);
+ let thumbnail;
+ if (this.state.loaded) {
+ const type = Utils.getFileType(fileInfo.extension);
- var thumbnail;
- if (type === 'image') {
- thumbnail = (
- <div
- ref={filename}
- className='post-image__load'
- />
- );
- } else {
- thumbnail = <div className={'file-icon ' + utils.getIconClassName(type)}/>;
- }
+ if (type === 'image') {
+ let className = 'post-image';
- var fileSizeString = '';
- if (this.state.fileSize < 0) {
- Client.getFileInfo(
- filename,
- (data) => {
- if (this.canSetState) {
- this.setState({fileSize: parseInt(data.size, 10)});
- }
- },
- () => {
- // Do nothing
+ if (fileInfo.width < Constants.THUMBNAIL_WIDTH && fileInfo.height < Constants.THUMBNAIL_HEIGHT) {
+ className += ' small';
+ } else {
+ className += ' normal';
}
- );
+
+ thumbnail = (
+ <div
+ className={className}
+ style={{
+ backgroundImage: `url(${FileStore.getFileThumbnailUrl(fileInfo.id)})`
+ }}
+ />
+ );
+ } else {
+ thumbnail = <div className={'file-icon ' + Utils.getIconClassName(type)}/>;
+ }
} else {
- fileSizeString = utils.fileSizeToString(this.state.fileSize);
+ thumbnail = <div className='post-image__load'/>;
}
- var filenameString = decodeURIComponent(utils.getFileName(filename));
- var trimmedFilename;
- if (filenameString.length > 35) {
- trimmedFilename = filenameString.substring(0, Math.min(35, filenameString.length)) + '...';
+ let trimmedFilename;
+ if (fileName.length > 35) {
+ trimmedFilename = fileName.substring(0, Math.min(35, fileName.length)) + '...';
} else {
- trimmedFilename = filenameString;
+ trimmedFilename = fileName;
}
- var filenameOverlay = (
- <OverlayTrigger
- delayShow={1000}
- placement='top'
- overlay={<Tooltip id='file-name__tooltip'>{this.props.intl.formatMessage(holders.download) + ' "' + filenameString + '"'}</Tooltip>}
- >
- <a
- href={fileUrl}
- download={filenameString}
- className='post-image__name'
- target='_blank'
- rel='noopener noreferrer'
- >
- {trimmedFilename}
- </a>
- </OverlayTrigger>
- );
+ let filenameOverlay;
if (this.props.compactDisplay) {
filenameOverlay = (
<OverlayTrigger
delayShow={1000}
placement='top'
- overlay={<Tooltip id='file-name__tooltip'>{filenameString}</Tooltip>}
+ overlay={<Tooltip id='file-name__tooltip'>{fileName}</Tooltip>}
>
<a
href='#'
@@ -214,13 +121,28 @@ class FileAttachment extends React.Component {
</a>
</OverlayTrigger>
);
+ } else {
+ filenameOverlay = (
+ <OverlayTrigger
+ delayShow={1000}
+ placement='top'
+ overlay={<Tooltip id='file-name__tooltip'>{Utils.localizeMessage('file_attachment.download', 'Download') + ' "' + fileName + '"'}</Tooltip>}
+ >
+ <a
+ href={fileUrl}
+ download={fileName}
+ className='post-image__name'
+ target='_blank'
+ rel='noopener noreferrer'
+ >
+ {trimmedFilename}
+ </a>
+ </OverlayTrigger>
+ );
}
return (
- <div
- className='post-image__column'
- key={filename}
- >
+ <div className='post-image__column'>
<a
className='post-image__thumbnail'
href='#'
@@ -233,17 +155,15 @@ class FileAttachment extends React.Component {
<div>
<a
href={fileUrl}
- download={filenameString}
+ download={fileName}
className='post-image__download'
target='_blank'
rel='noopener noreferrer'
>
- <span
- className='fa fa-download'
- />
+ <span className='fa fa-download'/>
</a>
- <span className='post-image__type'>{fileInfo.ext.toUpperCase()}</span>
- <span className='post-image__size'>{fileSizeString}</span>
+ <span className='post-image__type'>{fileInfo.extension.toUpperCase()}</span>
+ <span className='post-image__size'>{Utils.fileSizeToString(fileInfo.size)}</span>
</div>
</div>
</div>
@@ -252,10 +172,7 @@ class FileAttachment extends React.Component {
}
FileAttachment.propTypes = {
- intl: intlShape.isRequired,
-
- // a list of file pathes displayed by the parent FileAttachmentList
- filename: React.PropTypes.string.isRequired,
+ fileInfo: React.PropTypes.object.isRequired,
// the index of this attachment preview in the parent FileAttachmentList
index: React.PropTypes.number.isRequired,
@@ -264,6 +181,4 @@ FileAttachment.propTypes = {
handleImageClick: React.PropTypes.func,
compactDisplay: React.PropTypes.bool
-};
-
-export default injectIntl(FileAttachment);
+}; \ No newline at end of file
diff --git a/webapp/components/file_attachment_list.jsx b/webapp/components/file_attachment_list.jsx
index e4b841769..3df4684be 100644
--- a/webapp/components/file_attachment_list.jsx
+++ b/webapp/components/file_attachment_list.jsx
@@ -13,25 +13,34 @@ export default class FileAttachmentList extends React.Component {
this.handleImageClick = this.handleImageClick.bind(this);
- this.state = {showPreviewModal: false, startImgId: 0};
+ this.state = {showPreviewModal: false, startImgIndex: 0};
}
+
handleImageClick(indexClicked) {
- this.setState({showPreviewModal: true, startImgId: indexClicked});
+ this.setState({showPreviewModal: true, startImgIndex: indexClicked});
}
+
render() {
- var filenames = this.props.filenames;
+ const postFiles = [];
+ if (this.props.fileInfos && this.props.fileInfos.length > 0) {
+ for (let i = 0; i < Math.min(this.props.fileInfos.length, Constants.MAX_DISPLAY_FILES); i++) {
+ const fileInfo = this.props.fileInfos[i];
- var postFiles = [];
- for (var i = 0; i < filenames.length && i < Constants.MAX_DISPLAY_FILES; i++) {
- postFiles.push(
- <FileAttachment
- key={'file_attachment_' + i}
- filename={filenames[i]}
- index={i}
- handleImageClick={this.handleImageClick}
- compactDisplay={this.props.compactDisplay}
- />
- );
+ postFiles.push(
+ <FileAttachment
+ key={fileInfo.id}
+ fileInfo={this.props.fileInfos[i]}
+ index={i}
+ handleImageClick={this.handleImageClick}
+ compactDisplay={this.props.compactDisplay}
+ />
+ );
+ }
+ } else if (this.props.fileCount > 0) {
+ for (let i = 0; i < Math.min(this.props.fileCount, Constants.MAX_DISPLAY_FILES); i++) {
+ // Add a placeholder to avoid pop-in once we get the file infos for this post
+ postFiles.push(<div className='post-image__column post-image__column--placeholder'/>);
+ }
}
return (
@@ -42,10 +51,8 @@ export default class FileAttachmentList extends React.Component {
<ViewImageModal
show={this.state.showPreviewModal}
onModalDismissed={() => this.setState({showPreviewModal: false})}
- channelId={this.props.channelId}
- userId={this.props.userId}
- startId={this.state.startImgId}
- filenames={filenames}
+ startId={this.state.startImgIndex}
+ fileInfos={this.props.fileInfos}
/>
</div>
);
@@ -53,15 +60,7 @@ export default class FileAttachmentList extends React.Component {
}
FileAttachmentList.propTypes = {
-
- // a list of file pathes displayed by this
- filenames: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
-
- // the channel that this is part of
- channelId: React.PropTypes.string,
-
- // the user that owns the post that this is attached to
- userId: React.PropTypes.string,
-
+ fileCount: React.PropTypes.number.isRequired,
+ fileInfos: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
compactDisplay: React.PropTypes.bool
};
diff --git a/webapp/components/file_attachment_list_container.jsx b/webapp/components/file_attachment_list_container.jsx
new file mode 100644
index 000000000..f9ad3814c
--- /dev/null
+++ b/webapp/components/file_attachment_list_container.jsx
@@ -0,0 +1,90 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import * as AsyncClient from 'utils/async_client.jsx';
+import FileStore from 'stores/file_store.jsx';
+
+import FileAttachmentList from './file_attachment_list.jsx';
+
+export default class FileAttachmentListContainer extends React.Component {
+ static propTypes = {
+ post: React.PropTypes.object.isRequired,
+ compactDisplay: React.PropTypes.bool.isRequired
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.handleFileChange = this.handleFileChange.bind(this);
+
+ this.state = {
+ fileInfos: FileStore.getInfosForPost(props.post.id)
+ };
+ }
+
+ componentDidMount() {
+ FileStore.addChangeListener(this.handleFileChange);
+
+ if (this.props.post.id && !FileStore.hasInfosForPost(this.props.post.id)) {
+ AsyncClient.getFileInfosForPost(this.props.post.channel_id, this.props.post.id);
+ }
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.post.id !== this.props.post.id) {
+ this.setState({
+ fileInfos: FileStore.getInfosForPost(nextProps.post.id)
+ });
+
+ if (nextProps.post.id && !FileStore.hasInfosForPost(nextProps.post.id)) {
+ AsyncClient.getFileInfosForPost(nextProps.post.channel_id, nextProps.post.id);
+ }
+ }
+ }
+
+ shouldComponentUpdate(nextProps, nextState) {
+ if (this.props.post.id !== nextProps.post.id) {
+ return true;
+ }
+
+ if (this.props.compactDisplay !== nextProps.compactDisplay) {
+ return true;
+ }
+
+ // fileInfos are treated as immutable by the FileStore
+ if (nextState.fileInfos !== this.state.fileInfos) {
+ return true;
+ }
+
+ return false;
+ }
+
+ handleFileChange() {
+ this.setState({
+ fileInfos: FileStore.getInfosForPost(this.props.post.id)
+ });
+ }
+
+ componentWillUnmount() {
+ FileStore.removeChangeListener(this.handleFileChange);
+ }
+
+ render() {
+ let fileCount = 0;
+ if (this.props.post.file_ids) {
+ fileCount = this.props.post.file_ids.length;
+ } else if (this.props.post.filenames) {
+ fileCount = this.props.post.filenames.length;
+ }
+
+ return (
+ <FileAttachmentList
+ fileCount={fileCount}
+ fileInfos={this.state.fileInfos}
+ compactDisplay={this.props.compactDisplay}
+ />
+ );
+ }
+}
diff --git a/webapp/components/file_info_preview.jsx b/webapp/components/file_info_preview.jsx
index b3d16b6a6..51825ce5b 100644
--- a/webapp/components/file_info_preview.jsx
+++ b/webapp/components/file_info_preview.jsx
@@ -1,59 +1,59 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import React from 'react';
+
import * as Utils from 'utils/utils.jsx';
-import {defineMessages} from 'react-intl';
-import React from 'react';
-import {Link} from 'react-router/es6';
-
-const holders = defineMessages({
- type: {
- id: 'file_info_preview.type',
- defaultMessage: 'File type '
- },
- size: {
- id: 'file_info_preview.size',
- defaultMessage: 'Size '
- }
-});
+export default class FileInfoPreview extends React.Component {
+ shouldComponentUpdate(nextProps) {
+ if (nextProps.fileUrl !== this.props.fileUrl) {
+ return true;
+ }
-export default function FileInfoPreview({filename, fileUrl, fileInfo, formatMessage}) {
- // non-image files include a section providing details about the file
- const infoParts = [];
+ if (!Utils.areObjectsEqual(nextProps.fileInfo, this.props.fileInfo)) {
+ return true;
+ }
- if (fileInfo.extension !== '') {
- infoParts.push(formatMessage(holders.type) + fileInfo.extension.toUpperCase());
+ return false;
}
- infoParts.push(formatMessage(holders.size) + Utils.fileSizeToString(fileInfo.size));
-
- const infoString = infoParts.join(', ');
-
- const name = decodeURIComponent(Utils.getFileName(filename));
-
- return (
- <div className='file-details__container'>
- <Link
- className={'file-details__preview'}
- to={fileUrl}
- target='_blank'
- rel='noopener noreferrer'
- >
- <span className='file-details__preview-helper'/>
- <img src={Utils.getPreviewImagePath(filename)}/>
- </Link>
- <div className='file-details'>
- <div className='file-details__name'>{name}</div>
- <div className='file-details__info'>{infoString}</div>
+ render() {
+ const fileInfo = this.props.fileInfo;
+ const fileUrl = this.props.fileUrl;
+
+ // non-image files include a section providing details about the file
+ const infoParts = [];
+
+ if (fileInfo.extension !== '') {
+ infoParts.push(Utils.localizeMessage('file_info_preview.type', 'File type ') + fileInfo.extension.toUpperCase());
+ }
+
+ infoParts.push(Utils.localizeMessage('file_info_preview.size', 'Size ') + Utils.fileSizeToString(fileInfo.size));
+
+ const infoString = infoParts.join(', ');
+
+ return (
+ <div className='file-details__container'>
+ <a
+ className={'file-details__preview'}
+ to={fileUrl}
+ target='_blank'
+ rel='noopener noreferrer'
+ >
+ <span className='file-details__preview-helper'/>
+ <img src={Utils.getFileIconPath(fileInfo)}/>
+ </a>
+ <div className='file-details'>
+ <div className='file-details__name'>{fileInfo.name}</div>
+ <div className='file-details__info'>{infoString}</div>
+ </div>
</div>
- </div>
- );
+ );
+ }
}
FileInfoPreview.propTypes = {
- filename: React.PropTypes.string.isRequired,
- fileUrl: React.PropTypes.string.isRequired,
fileInfo: React.PropTypes.object.isRequired,
- formatMessage: React.PropTypes.func.isRequired
+ fileUrl: React.PropTypes.string.isRequired
};
diff --git a/webapp/components/file_preview.jsx b/webapp/components/file_preview.jsx
index 46ce43a6f..53cec7f7b 100644
--- a/webapp/components/file_preview.jsx
+++ b/webapp/components/file_preview.jsx
@@ -1,6 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import FileStore from 'stores/file_store.jsx';
import ReactDOM from 'react-dom';
import * as Utils from 'utils/utils.jsx';
@@ -21,63 +22,43 @@ export default class FilePreview extends React.Component {
}
}
- handleRemove(e) {
- var previewDiv = e.target.parentNode.parentNode;
-
- if (previewDiv.hasAttribute('data-filename')) {
- this.props.onRemove(previewDiv.getAttribute('data-filename'));
- } else if (previewDiv.hasAttribute('data-client-id')) {
- this.props.onRemove(previewDiv.getAttribute('data-client-id'));
- }
+ handleRemove(id) {
+ this.props.onRemove(id);
}
render() {
var previews = [];
- this.props.files.forEach((fullFilename) => {
- var filename = fullFilename;
- var originalFilename = filename;
- var filenameSplit = filename.split('.');
- var ext = filenameSplit[filenameSplit.length - 1];
- var type = Utils.getFileType(ext);
-
- filename = Utils.getFileUrl(filename);
+ this.props.fileInfos.forEach((info) => {
+ const type = Utils.getFileType(info.extension);
+ let className = 'file-preview';
+ let previewImage;
if (type === 'image') {
- previews.push(
- <div
- key={filename}
- className='file-preview'
- data-filename={originalFilename}
- >
- <img
- className='file-preview__image'
- src={filename}
- />
- <a
- className='file-preview__remove'
- onClick={this.handleRemove}
- >
- <i className='fa fa-remove'/>
- </a>
- </div>
+ previewImage = (
+ <img
+ className='file-preview__image'
+ src={FileStore.getFileUrl(info.id)}
+ />
);
} else {
- previews.push(
- <div
- key={filename}
- className='file-preview custom-file'
- data-filename={originalFilename}
- >
- <div className={'file-icon ' + Utils.getIconClassName(type)}/>
- <a
- className='file-preview__remove'
- onClick={this.handleRemove}
- >
- <i className='fa fa-remove'/>
- </a>
- </div>
- );
+ className += ' custom-file';
+ previewImage = <div className={'file-icon ' + Utils.getIconClassName(type)}/>;
}
+
+ previews.push(
+ <div
+ key={info.id}
+ className={className}
+ >
+ {previewImage}
+ <a
+ className='file-preview__remove'
+ onClick={this.handleRemove.bind(this, info.id)}
+ >
+ <i className='fa fa-remove'/>
+ </a>
+ </div>
+ );
});
this.props.uploadsInProgress.forEach((clientId) => {
@@ -94,7 +75,7 @@ export default class FilePreview extends React.Component {
/>
<a
className='file-preview__remove'
- onClick={this.handleRemove}
+ onClick={this.handleRemove.bind(this, clientId)}
>
<i className='fa fa-remove'/>
</a>
@@ -111,11 +92,11 @@ export default class FilePreview extends React.Component {
}
FilePreview.defaultProps = {
- files: [],
+ fileInfos: [],
uploadsInProgress: []
};
FilePreview.propTypes = {
onRemove: React.PropTypes.func.isRequired,
- files: React.PropTypes.array,
+ fileInfos: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
uploadsInProgress: React.PropTypes.array
};
diff --git a/webapp/components/file_upload.jsx b/webapp/components/file_upload.jsx
index 39abec7e4..9eff25ab5 100644
--- a/webapp/components/file_upload.jsx
+++ b/webapp/components/file_upload.jsx
@@ -49,13 +49,12 @@ class FileUpload extends React.Component {
this.keyUpload = this.keyUpload.bind(this);
this.state = {
- maxFileSize: global.window.mm_config.MaxFileSize,
requests: {}
};
}
fileUploadSuccess(channelId, data) {
- this.props.onFileUpload(data.filenames, data.client_ids, channelId);
+ this.props.onFileUpload(data.file_infos, data.client_ids, channelId);
const requests = Object.assign({}, this.state.requests);
for (var j = 0; j < data.client_ids.length; j++) {
@@ -81,7 +80,7 @@ class FileUpload extends React.Component {
const tooLargeFiles = [];
for (let i = 0; i < files.length && numUploads < uploadsRemaining; i++) {
- if (files[i].size > this.state.maxFileSize) {
+ if (files[i].size > global.mm_config.MaxFileSize) {
tooLargeFiles.push(files[i]);
continue;
}
@@ -112,9 +111,9 @@ class FileUpload extends React.Component {
} else if (tooLargeFiles.length > 1) {
var tooLargeFilenames = tooLargeFiles.map((file) => file.name).join(', ');
- this.props.onUploadError(formatMessage(holders.filesAbove, {max: (this.state.maxFileSize / 1048576), filenames: tooLargeFilenames}));
+ this.props.onUploadError(formatMessage(holders.filesAbove, {max: (global.mm_config.MaxFileSize / 1048576), filenames: tooLargeFilenames}));
} else if (tooLargeFiles.length > 0) {
- this.props.onUploadError(formatMessage(holders.fileAbove, {max: (this.state.maxFileSize / 1048576), filename: tooLargeFiles[0].name}));
+ this.props.onUploadError(formatMessage(holders.fileAbove, {max: (global.mm_config.MaxFileSize / 1048576), filename: tooLargeFiles[0].name}));
}
}
diff --git a/webapp/components/get_public_link_modal.jsx b/webapp/components/get_public_link_modal.jsx
index 49fd891be..851a78f80 100644
--- a/webapp/components/get_public_link_modal.jsx
+++ b/webapp/components/get_public_link_modal.jsx
@@ -23,7 +23,7 @@ export default class GetPublicLinkModal extends React.Component {
this.state = {
show: false,
- filename: '',
+ fileId: '',
link: ''
};
}
@@ -34,7 +34,7 @@ export default class GetPublicLinkModal extends React.Component {
componentDidUpdate(prevProps, prevState) {
if (this.state.show && !prevState.show) {
- AsyncClient.getPublicLink(decodeURIComponent(this.state.filename), this.handlePublicLink);
+ AsyncClient.getPublicLink(this.state.fileId, this.handlePublicLink);
}
}
@@ -51,7 +51,7 @@ export default class GetPublicLinkModal extends React.Component {
handleToggle(value, args) {
this.setState({
show: value,
- filename: args.filename,
+ fileId: args.fileId,
link: ''
});
}
diff --git a/webapp/components/pdf_preview.jsx b/webapp/components/pdf_preview.jsx
index 7f0f06c03..2cb0a324c 100644
--- a/webapp/components/pdf_preview.jsx
+++ b/webapp/components/pdf_preview.jsx
@@ -3,8 +3,6 @@
import FileInfoPreview from './file_info_preview.jsx';
-import * as Utils from 'utils/utils.jsx';
-
import loadingGif from 'images/load.gif';
import React from 'react';
@@ -109,18 +107,8 @@ export default class PDFPreview extends React.Component {
}
}
- static support(filename) {
- const fileInfo = Utils.splitFileLocation(filename);
- const ext = fileInfo.ext;
- if (!ext) {
- return false;
- }
-
- if (ext === 'pdf') {
- return true;
- }
-
- return false;
+ static supports(fileInfo) {
+ return fileInfo.extension === 'pdf';
}
render() {
@@ -138,10 +126,8 @@ export default class PDFPreview extends React.Component {
if (!this.state.success) {
return (
<FileInfoPreview
- filename={this.props.filename}
- fileUrl={this.props.fileUrl}
fileInfo={this.props.fileInfo}
- formatMessage={this.props.formatMessage}
+ fileUrl={this.props.fileUrl}
/>
);
}
@@ -185,8 +171,6 @@ export default class PDFPreview extends React.Component {
}
PDFPreview.propTypes = {
- filename: React.PropTypes.string.isRequired,
- fileUrl: React.PropTypes.string.isRequired,
fileInfo: React.PropTypes.object.isRequired,
- formatMessage: React.PropTypes.func.isRequired
+ fileUrl: React.PropTypes.string.isRequired
};
diff --git a/webapp/components/post_view/components/commented_on_files_message_container.jsx b/webapp/components/post_view/components/commented_on_files_message_container.jsx
new file mode 100644
index 000000000..5325a7644
--- /dev/null
+++ b/webapp/components/post_view/components/commented_on_files_message_container.jsx
@@ -0,0 +1,88 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import * as AsyncClient from 'utils/async_client.jsx';
+import FileStore from 'stores/file_store.jsx';
+import * as Utils from 'utils/utils.jsx';
+
+export default class CommentedOnFilesMessageContainer extends React.Component {
+ static propTypes = {
+ parentPostChannelId: React.PropTypes.string.isRequired,
+ parentPostId: React.PropTypes.string.isRequired
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.handleFileChange = this.handleFileChange.bind(this);
+
+ this.state = {
+ fileInfos: FileStore.getInfosForPost(this.props.parentPostId)
+ };
+ }
+
+ componentDidMount() {
+ FileStore.addChangeListener(this.handleFileChange);
+
+ if (!FileStore.hasInfosForPost(this.props.parentPostId)) {
+ AsyncClient.getFileInfosForPost(this.props.parentPostChannelId, this.props.parentPostId);
+ }
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.parentPostId !== this.props.parentPostId) {
+ this.setState({
+ fileInfos: FileStore.getInfosForPost(this.props.parentPostId)
+ });
+
+ if (!FileStore.hasInfosForPost(this.props.parentPostId)) {
+ AsyncClient.getFileInfosForPost(this.props.parentPostChannelId, this.props.parentPostId);
+ }
+ }
+ }
+
+ shouldComponentUpdate(nextProps, nextState) {
+ if (nextProps.parentPostId !== this.props.parentPostId) {
+ return true;
+ }
+
+ if (nextProps.parentPostChannelId !== this.props.parentPostChannelId) {
+ return true;
+ }
+
+ // fileInfos are treated as immutable by the FileStore
+ if (nextState.fileInfos !== this.state.fileInfos) {
+ return true;
+ }
+
+ return false;
+ }
+
+ handleFileChange() {
+ this.setState({
+ fileInfos: FileStore.getInfosForPost(this.props.parentPostId)
+ });
+ }
+
+ componentWillUnmount() {
+ FileStore.removeChangeListener(this.handleFileChange);
+ }
+
+ render() {
+ let message = ' ';
+
+ if (this.state.fileInfos && this.state.fileInfos.length > 0) {
+ message = this.state.fileInfos[0].name;
+
+ if (this.state.fileInfos.length === 2) {
+ message += Utils.localizeMessage('post_body.plusOne', ' plus 1 other file');
+ } else if (this.state.fileInfos.length > 2) {
+ message += Utils.localizeMessage('post_body.plusMore', ' plus {count} other files').replace('{count}', (this.state.fileInfos.length - 1).toString());
+ }
+ }
+
+ return <span>{message}</span>;
+ }
+}
diff --git a/webapp/components/post_view/components/post_body.jsx b/webapp/components/post_view/components/post_body.jsx
index 5c02e9c40..c23939c1f 100644
--- a/webapp/components/post_view/components/post_body.jsx
+++ b/webapp/components/post_view/components/post_body.jsx
@@ -1,11 +1,12 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import FileAttachmentList from 'components/file_attachment_list.jsx';
import UserStore from 'stores/user_store.jsx';
import * as Utils from 'utils/utils.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import Constants from 'utils/constants.jsx';
+import CommentedOnFilesMessageContainer from './commented_on_files_message_container.jsx';
+import FileAttachmentListContainer from 'components/file_attachment_list_container.jsx';
import PostBodyAdditionalContent from './post_body_additional_content.jsx';
import PostMessageContainer from './post_message_container.jsx';
import PendingPostOptions from './pending_post_options.jsx';
@@ -22,6 +23,7 @@ export default class PostBody extends React.Component {
this.removePost = this.removePost.bind(this);
}
+
shouldComponentUpdate(nextProps) {
if (nextProps.isCommentMention !== this.props.isCommentMention) {
return true;
@@ -56,7 +58,6 @@ export default class PostBody extends React.Component {
render() {
const post = this.props.post;
- const filenames = this.props.post.filenames;
const parentPost = this.props.parentPost;
let comment = '';
@@ -94,14 +95,13 @@ export default class PostBody extends React.Component {
let message = '';
if (parentPost.message) {
message = Utils.replaceHtmlEntities(parentPost.message);
- } else if (parentPost.filenames.length) {
- message = parentPost.filenames[0].split('/').pop();
-
- if (parentPost.filenames.length === 2) {
- message += Utils.localizeMessage('post_body.plusOne', ' plus 1 other file');
- } else if (parentPost.filenames.length > 2) {
- message += Utils.localizeMessage('post_body.plusMore', ' plus {count} other files').replace('{count}', (parentPost.filenames.length - 1).toString());
- }
+ } else if (parentPost.file_ids && parentPost.file_ids.length > 0) {
+ message = (
+ <CommentedOnFilesMessageContainer
+ parentPostChannelId={parentPost.channel_id}
+ parentPostId={parentPost.id}
+ />
+ );
}
comment = (
@@ -140,14 +140,11 @@ export default class PostBody extends React.Component {
);
}
- let fileAttachmentHolder = '';
- if (filenames && filenames.length > 0) {
+ let fileAttachmentHolder = null;
+ if ((post.file_ids && post.file_ids.length > 0) || (post.filenames && post.filenames.length > 0)) {
fileAttachmentHolder = (
- <FileAttachmentList
-
- filenames={filenames}
- channelId={post.channel_id}
- userId={post.user_id}
+ <FileAttachmentListContainer
+ post={post}
compactDisplay={this.props.compactDisplay}
/>
);
diff --git a/webapp/components/rhs_comment.jsx b/webapp/components/rhs_comment.jsx
index e1af1227b..18e4b4d1c 100644
--- a/webapp/components/rhs_comment.jsx
+++ b/webapp/components/rhs_comment.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
import UserProfile from './user_profile.jsx';
-import FileAttachmentList from './file_attachment_list.jsx';
+import FileAttachmentListContainer from './file_attachment_list_container.jsx';
import PendingPostOptions from 'components/post_view/components/pending_post_options.jsx';
import PostMessageContainer from 'components/post_view/components/post_message_container.jsx';
import ProfilePicture from 'components/profile_picture.jsx';
@@ -295,13 +295,11 @@ export default class RhsComment extends React.Component {
var dropdown = this.createDropdown();
- var fileAttachment;
- if (post.filenames && post.filenames.length > 0) {
+ let fileAttachment = null;
+ if (post.file_ids && post.file_ids.length > 0) {
fileAttachment = (
- <FileAttachmentList
- filenames={post.filenames}
- channelId={post.channel_id}
- userId={post.user_id}
+ <FileAttachmentListContainer
+ post={post}
compactDisplay={this.props.compactDisplay}
/>
);
diff --git a/webapp/components/rhs_root_post.jsx b/webapp/components/rhs_root_post.jsx
index 09ab17ba5..983469f50 100644
--- a/webapp/components/rhs_root_post.jsx
+++ b/webapp/components/rhs_root_post.jsx
@@ -4,7 +4,7 @@
import UserProfile from './user_profile.jsx';
import PostBodyAdditionalContent from 'components/post_view/components/post_body_additional_content.jsx';
import PostMessageContainer from 'components/post_view/components/post_message_container.jsx';
-import FileAttachmentList from './file_attachment_list.jsx';
+import FileAttachmentListContainer from './file_attachment_list_container.jsx';
import ProfilePicture from 'components/profile_picture.jsx';
import ChannelStore from 'stores/channel_store.jsx';
@@ -242,13 +242,11 @@ export default class RhsRootPost extends React.Component {
);
}
- var fileAttachment;
- if (post.filenames && post.filenames.length > 0) {
+ let fileAttachment = null;
+ if (post.file_ids && post.file_ids.length > 0) {
fileAttachment = (
- <FileAttachmentList
- filenames={post.filenames}
- channelId={post.channel_id}
- userId={post.user_id}
+ <FileAttachmentListContainer
+ post={post}
compactDisplay={this.props.compactDisplay}
/>
);
diff --git a/webapp/components/view_image.jsx b/webapp/components/view_image.jsx
index c9f558725..385138d54 100644
--- a/webapp/components/view_image.jsx
+++ b/webapp/components/view_image.jsx
@@ -11,7 +11,6 @@ import * as GlobalActions from 'actions/global_actions.jsx';
import FileStore from 'stores/file_store.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
@@ -19,19 +18,11 @@ const KeyCodes = Constants.KeyCodes;
import $ from 'jquery';
import React from 'react';
-import {intlShape, injectIntl, defineMessages} from 'react-intl';
import {Modal} from 'react-bootstrap';
import loadingGif from 'images/load.gif';
-const holders = defineMessages({
- loading: {
- id: 'view_image.loading',
- defaultMessage: 'Loading '
- }
-});
-
-class ViewImageModal extends React.Component {
+export default class ViewImageModal extends React.Component {
constructor(props) {
super(props);
@@ -45,18 +36,15 @@ class ViewImageModal extends React.Component {
this.onModalShown = this.onModalShown.bind(this);
this.onModalHidden = this.onModalHidden.bind(this);
- this.onFileStoreChange = this.onFileStoreChange.bind(this);
-
this.handleGetPublicLink = this.handleGetPublicLink.bind(this);
this.onMouseEnterImage = this.onMouseEnterImage.bind(this);
this.onMouseLeaveImage = this.onMouseLeaveImage.bind(this);
this.state = {
imgId: this.props.startId,
- fileInfo: null,
imgHeight: '100%',
- loaded: Utils.fillArray(false, this.props.filenames.length),
- progress: Utils.fillArray(0, this.props.filenames.length),
+ loaded: Utils.fillArray(false, this.props.fileInfos.length),
+ progress: Utils.fillArray(0, this.props.fileInfos.length),
showFooter: false
};
}
@@ -66,7 +54,7 @@ class ViewImageModal extends React.Component {
e.stopPropagation();
}
let id = this.state.imgId + 1;
- if (id > this.props.filenames.length - 1) {
+ if (id > this.props.fileInfos.length - 1) {
id = 0;
}
this.showImage(id);
@@ -78,7 +66,7 @@ class ViewImageModal extends React.Component {
}
let id = this.state.imgId - 1;
if (id < 0) {
- id = this.props.filenames.length - 1;
+ id = this.props.fileInfos.length - 1;
}
this.showImage(id);
}
@@ -95,8 +83,6 @@ class ViewImageModal extends React.Component {
$(window).on('keyup', this.handleKeyPress);
this.showImage(nextProps.startId);
-
- FileStore.addChangeListener(this.onFileStoreChange);
}
onModalHidden() {
@@ -105,8 +91,6 @@ class ViewImageModal extends React.Component {
if (this.refs.video) {
this.refs.video.stop();
}
-
- FileStore.removeChangeListener(this.onFileStoreChange);
}
componentWillReceiveProps(nextProps) {
@@ -116,64 +100,36 @@ class ViewImageModal extends React.Component {
this.onModalHidden();
}
- if (!Utils.areObjectsEqual(this.props.filenames, nextProps.filenames)) {
+ if (this.props.fileInfos !== nextProps.fileInfos) {
this.setState({
- loaded: Utils.fillArray(false, nextProps.filenames.length),
- progress: Utils.fillArray(0, nextProps.filenames.length)
+ loaded: Utils.fillArray(false, nextProps.fileInfos.length),
+ progress: Utils.fillArray(0, nextProps.fileInfos.length)
});
}
}
- onFileStoreChange(filename) {
- const id = this.props.filenames.indexOf(filename);
-
- if (id !== -1) {
- if (id === this.state.imgId) {
- this.setState({
- fileInfo: FileStore.getInfo(filename)
- });
- }
-
- if (!this.state.loaded[id]) {
- this.loadImage(id, filename);
- }
- }
- }
-
showImage(id) {
this.setState({imgId: id});
const imgHeight = $(window).height() - 100;
this.setState({imgHeight});
- const filename = this.props.filenames[id];
-
- if (!FileStore.hasInfo(filename)) {
- // the image will actually be loaded once we know what we need to load
- AsyncClient.getFileInfo(filename);
- return;
- }
-
- this.setState({
- fileInfo: FileStore.getInfo(filename)
- });
-
if (!this.state.loaded[id]) {
- this.loadImage(id, filename);
+ this.loadImage(id);
}
}
- loadImage(id, filename) {
- const fileInfo = FileStore.getInfo(filename);
+ loadImage(index) {
+ const fileInfo = this.props.fileInfos[index];
const fileType = Utils.getFileType(fileInfo.extension);
if (fileType === 'image') {
let previewUrl;
if (fileInfo.has_image_preview) {
- previewUrl = Utils.getPreviewImagePath(filename);
+ previewUrl = FileStore.getFilePreviewUrl(fileInfo.id);
} else {
// some images (eg animated gifs) just show the file itself and not a preview
- previewUrl = Utils.getFileUrl(filename);
+ previewUrl = FileStore.getFileUrl(fileInfo.id);
}
const img = new Image();
@@ -181,19 +137,19 @@ class ViewImageModal extends React.Component {
previewUrl,
() => {
const progress = this.state.progress;
- progress[id] = img.completedPercentage;
+ progress[index] = img.completedPercentage;
this.setState({progress});
}
);
img.onload = () => {
const loaded = this.state.loaded;
- loaded[id] = true;
+ loaded[index] = true;
this.setState({loaded});
};
} else {
// there's nothing to load for non-image files
var loaded = this.state.loaded;
- loaded[id] = true;
+ loaded[index] = true;
this.setState({loaded});
}
}
@@ -201,7 +157,7 @@ class ViewImageModal extends React.Component {
handleGetPublicLink() {
this.props.onModalDismissed();
- GlobalActions.showGetPublicLinkModal(this.props.filenames[this.state.imgId]);
+ GlobalActions.showGetPublicLinkModal(this.props.fileInfos[this.state.imgId].id);
}
onMouseEnterImage() {
@@ -213,63 +169,52 @@ class ViewImageModal extends React.Component {
}
render() {
- if (this.props.filenames.length < 1 || this.props.filenames.length - 1 < this.state.imgId) {
- return <div/>;
+ if (this.props.fileInfos.length < 1 || this.props.fileInfos.length - 1 < this.state.imgId) {
+ return null;
}
- const filename = this.props.filenames[this.state.imgId];
- const fileUrl = Utils.getFileUrl(filename);
+ const fileInfo = this.props.fileInfos[this.state.imgId];
+ const fileUrl = FileStore.getFileUrl(fileInfo.id);
- var content;
+ let content;
if (this.state.loaded[this.state.imgId]) {
- // this.state.fileInfo is for the current image and we shoudl have it before we load the image
- const fileInfo = this.state.fileInfo;
const fileType = Utils.getFileType(fileInfo.extension);
if (fileType === 'image') {
content = (
<ImagePreview
- filename={filename}
- fileUrl={fileUrl}
fileInfo={fileInfo}
+ fileUrl={fileUrl}
maxHeight={this.state.imgHeight}
/>
);
} else if (fileType === 'video' || fileType === 'audio') {
content = (
<AudioVideoPreview
- filename={filename}
+ fileInfo={fileInfo}
fileUrl={fileUrl}
- fileInfo={this.state.fileInfo}
maxHeight={this.state.imgHeight}
- formatMessage={this.props.intl.formatMessage}
/>
);
- } else if (PDFPreview.support(filename)) {
+ } else if (PDFPreview.supports(fileInfo)) {
content = (
<PDFPreview
- filename={filename}
- fileUrl={fileUrl}
fileInfo={fileInfo}
- formatMessage={this.props.intl.formatMessage}
+ fileUrl={fileUrl}
/>
);
- } else if (CodePreview.support(filename)) {
+ } else if (CodePreview.supports(fileInfo)) {
content = (
<CodePreview
- filename={filename}
- fileUrl={fileUrl}
fileInfo={fileInfo}
- formatMessage={this.props.intl.formatMessage}
+ fileUrl={fileUrl}
/>
);
} else {
content = (
<FileInfoPreview
- filename={filename}
- fileUrl={fileUrl}
fileInfo={fileInfo}
- formatMessage={this.props.intl.formatMessage}
+ fileUrl={fileUrl}
/>
);
}
@@ -280,14 +225,14 @@ class ViewImageModal extends React.Component {
content = (
<LoadingImagePreview
progress={progress}
- loading={this.props.intl.formatMessage(holders.loading)}
+ loading={Utils.localizeMessage('view_image.loading', 'Loading ')}
/>
);
}
let leftArrow = null;
let rightArrow = null;
- if (this.props.filenames.length > 1) {
+ if (this.props.fileInfos.length > 1) {
leftArrow = (
<a
ref='previewArrowLeft'
@@ -346,8 +291,8 @@ class ViewImageModal extends React.Component {
<ViewImagePopoverBar
show={this.state.showFooter}
fileId={this.state.imgId}
- totalFiles={this.props.filenames.length}
- filename={name}
+ totalFiles={this.props.fileInfos.length}
+ filename={fileInfo.name}
fileURL={fileUrl}
onGetPublicLink={this.handleGetPublicLink}
/>
@@ -363,19 +308,13 @@ class ViewImageModal extends React.Component {
ViewImageModal.defaultProps = {
show: false,
- filenames: [],
- channelId: '',
- userId: '',
+ fileInfos: [],
startId: 0
};
ViewImageModal.propTypes = {
- intl: intlShape.isRequired,
show: React.PropTypes.bool.isRequired,
onModalDismissed: React.PropTypes.func.isRequired,
- filenames: React.PropTypes.array,
- modalId: React.PropTypes.string,
- channelId: React.PropTypes.string,
- userId: React.PropTypes.string,
+ fileInfos: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
startId: React.PropTypes.number
};
@@ -405,10 +344,10 @@ LoadingImagePreview.propTypes = {
loading: React.PropTypes.string
};
-function ImagePreview({filename, fileUrl, fileInfo, maxHeight}) {
+function ImagePreview({fileInfo, fileUrl, maxHeight}) {
let previewUrl;
if (fileInfo.has_preview_image) {
- previewUrl = Utils.getPreviewImagePath(filename);
+ previewUrl = FileStore.getFilePreviewUrl(fileInfo.id);
} else {
previewUrl = fileUrl;
}
@@ -429,10 +368,7 @@ function ImagePreview({filename, fileUrl, fileInfo, maxHeight}) {
}
ImagePreview.propTypes = {
- filename: React.PropTypes.string.isRequired,
- fileUrl: React.PropTypes.string.isRequired,
fileInfo: React.PropTypes.object.isRequired,
+ fileUrl: React.PropTypes.string.isRequired,
maxHeight: React.PropTypes.number.isRequired
};
-
-export default injectIntl(ViewImageModal);