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
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
a2deeed597
Коммит
8a0e649f98
@@ -294,11 +294,11 @@ export function showGetPostLinkModal(post) {
|
||||
});
|
||||
}
|
||||
|
||||
export function showGetPublicLinkModal(filename) {
|
||||
export function showGetPublicLinkModal(fileId) {
|
||||
AppDispatcher.handleViewAction({
|
||||
type: ActionTypes.TOGGLE_GET_PUBLIC_LINK_MODAL,
|
||||
value: true,
|
||||
filename
|
||||
fileId
|
||||
});
|
||||
}
|
||||
|
||||
@@ -388,7 +388,6 @@ export function sendEphemeralPost(message, channelId) {
|
||||
type: Constants.POST_TYPE_EPHEMERAL,
|
||||
create_at: timestamp,
|
||||
update_at: timestamp,
|
||||
filenames: [],
|
||||
props: {}
|
||||
};
|
||||
|
||||
|
||||
@@ -113,10 +113,14 @@ export default class Client {
|
||||
return `${this.url}${this.urlVersion}/users`;
|
||||
}
|
||||
|
||||
getFilesRoute() {
|
||||
getTeamFilesRoute() {
|
||||
return `${this.url}${this.urlVersion}/teams/${this.getTeamId()}/files`;
|
||||
}
|
||||
|
||||
getFileRoute(fileId) {
|
||||
return `${this.url}${this.urlVersion}/files/${fileId}`;
|
||||
}
|
||||
|
||||
getOAuthRoute() {
|
||||
return `${this.url}${this.urlVersion}/oauth`;
|
||||
}
|
||||
@@ -1520,34 +1524,20 @@ export default class Client {
|
||||
end(this.handleResponse.bind(this, 'getFlaggedPosts', success, error));
|
||||
}
|
||||
|
||||
getFileInfosForPost(channelId, postId, success, error) {
|
||||
request.
|
||||
get(`${this.getChannelNeededRoute(channelId)}/posts/${postId}/get_file_infos`).
|
||||
set(this.defaultHeaders).
|
||||
type('application/json').
|
||||
accept('application/json').
|
||||
end(this.handleResponse.bind(this, 'getFileInfosForPost', success, error));
|
||||
}
|
||||
|
||||
// Routes for Files
|
||||
|
||||
getFileInfo(filename, success, error) {
|
||||
request.
|
||||
get(`${this.getFilesRoute()}/get_info${filename}`).
|
||||
set(this.defaultHeaders).
|
||||
type('application/json').
|
||||
accept('application/json').
|
||||
end(this.handleResponse.bind(this, 'getFileInfo', success, error));
|
||||
}
|
||||
|
||||
getPublicLink(filename, success, error) {
|
||||
const data = {
|
||||
filename
|
||||
};
|
||||
|
||||
request.
|
||||
post(`${this.getFilesRoute()}/get_public_link`).
|
||||
set(this.defaultHeaders).
|
||||
type('application/json').
|
||||
accept('application/json').
|
||||
send(data).
|
||||
end(this.handleResponse.bind(this, 'getPublicLink', success, error));
|
||||
}
|
||||
|
||||
uploadFile(file, filename, channelId, clientId, success, error) {
|
||||
return request.
|
||||
post(`${this.getFilesRoute()}/upload`).
|
||||
post(`${this.getTeamFilesRoute()}/upload`).
|
||||
set(this.defaultHeaders).
|
||||
attach('files', file, filename).
|
||||
field('channel_id', channelId).
|
||||
@@ -1556,6 +1546,51 @@ export default class Client {
|
||||
end(this.handleResponse.bind(this, 'uploadFile', success, error));
|
||||
}
|
||||
|
||||
getFile(fileId, success, error) {
|
||||
request.
|
||||
get(`${this.getFileRoute(fileId)}/get`).
|
||||
set(this.defaultHeaders).
|
||||
type('application/json').
|
||||
accept('application/json').
|
||||
end(this.handleResponse.bind(this, 'getFile', success, error));
|
||||
}
|
||||
|
||||
getFileThumbnail(fileId, success, error) {
|
||||
request.
|
||||
get(`${this.getFileRoute(fileId)}/get_thumbnail`).
|
||||
set(this.defaultHeaders).
|
||||
type('application/json').
|
||||
accept('application/json').
|
||||
end(this.handleResponse.bind(this, 'getFileThumbnail', success, error));
|
||||
}
|
||||
|
||||
getFilePreview(fileId, success, error) {
|
||||
request.
|
||||
get(`${this.getFileRoute(fileId)}/get`).
|
||||
set(this.defaultHeaders).
|
||||
type('application/json').
|
||||
accept('application/json').
|
||||
end(this.handleResponse.bind(this, 'getFilePreview', success, error));
|
||||
}
|
||||
|
||||
getFileInfo(fileId, success, error) {
|
||||
request.
|
||||
get(`${this.getFileRoute(fileId)}/get_info`).
|
||||
set(this.defaultHeaders).
|
||||
type('application/json').
|
||||
accept('application/json').
|
||||
end(this.handleResponse.bind(this, 'getFileInfo', success, error));
|
||||
}
|
||||
|
||||
getPublicLink(fileId, success, error) {
|
||||
request.
|
||||
get(`${this.getFileRoute(fileId)}/get_public_link`).
|
||||
set(this.defaultHeaders).
|
||||
type('application/json').
|
||||
accept('application/json').
|
||||
end(this.handleResponse.bind(this, 'getPublicLink', success, error));
|
||||
}
|
||||
|
||||
// Routes for OAuth
|
||||
|
||||
registerOAuthApp(app, success, error) {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.fileInfo.id !== this.props.fileInfo.id) {
|
||||
this.setState({
|
||||
loaded: Utils.getFileType(nextProps.fileInfo.extension) !== 'image'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.filename !== prevProps.filename) {
|
||||
if (!this.state.loaded && this.props.fileInfo.id !== prevProps.fileInfo.id) {
|
||||
this.loadFiles();
|
||||
}
|
||||
}
|
||||
|
||||
loadFiles() {
|
||||
this.canSetState = true;
|
||||
const fileInfo = this.props.fileInfo;
|
||||
const fileType = Utils.getFileType(fileInfo.extension);
|
||||
|
||||
var filename = this.props.filename;
|
||||
if (fileType === 'image') {
|
||||
const thumbnailUrl = FileStore.getFileThumbnailUrl(fileInfo.id);
|
||||
|
||||
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)));
|
||||
}
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
this.setState({loaded: true});
|
||||
};
|
||||
img.load(thumbnailUrl);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
$(imgDiv).css('background-image', 'url(' + url + '_thumb.jpg)');
|
||||
}
|
||||
}
|
||||
removeBackgroundImage(name) {
|
||||
if (name in this.refs) {
|
||||
$(ReactDOM.findDOMNode(this.refs[name])).css('background-image', 'initial');
|
||||
}
|
||||
}
|
||||
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);
|
||||
};
|
||||
@@ -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});
|
||||
}
|
||||
render() {
|
||||
var filenames = this.props.filenames;
|
||||
|
||||
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}
|
||||
/>
|
||||
);
|
||||
handleImageClick(indexClicked) {
|
||||
this.setState({showPreviewModal: true, startImgIndex: indexClicked});
|
||||
}
|
||||
|
||||
render() {
|
||||
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];
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
90
webapp/components/file_attachment_list_container.jsx
Обычный файл
90
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
export default class FileInfoPreview extends React.Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
if (nextProps.fileUrl !== this.props.fileUrl) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const holders = defineMessages({
|
||||
type: {
|
||||
id: 'file_info_preview.type',
|
||||
defaultMessage: 'File type '
|
||||
},
|
||||
size: {
|
||||
id: 'file_info_preview.size',
|
||||
defaultMessage: 'Size '
|
||||
}
|
||||
});
|
||||
if (!Utils.areObjectsEqual(nextProps.fileInfo, this.props.fileInfo)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
export default function FileInfoPreview({filename, fileUrl, fileInfo, formatMessage}) {
|
||||
// non-image files include a section providing details about the file
|
||||
const infoParts = [];
|
||||
|
||||
if (fileInfo.extension !== '') {
|
||||
infoParts.push(formatMessage(holders.type) + fileInfo.extension.toUpperCase());
|
||||
return false;
|
||||
}
|
||||
|
||||
infoParts.push(formatMessage(holders.size) + Utils.fileSizeToString(fileInfo.size));
|
||||
render() {
|
||||
const fileInfo = this.props.fileInfo;
|
||||
const fileUrl = this.props.fileUrl;
|
||||
|
||||
const infoString = infoParts.join(', ');
|
||||
// non-image files include a section providing details about the file
|
||||
const infoParts = [];
|
||||
|
||||
const name = decodeURIComponent(Utils.getFileName(filename));
|
||||
if (fileInfo.extension !== '') {
|
||||
infoParts.push(Utils.localizeMessage('file_info_preview.type', 'File type ') + fileInfo.extension.toUpperCase());
|
||||
}
|
||||
|
||||
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>
|
||||
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
|
||||
};
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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: ''
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -166,6 +166,10 @@
|
||||
margin: 5px 10px 5px 0;
|
||||
position: relative;
|
||||
width: 240px;
|
||||
|
||||
&--placeholder {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.post-image__load {
|
||||
|
||||
@@ -16,41 +16,58 @@ class FileStore extends EventEmitter {
|
||||
this.handleEventPayload = this.handleEventPayload.bind(this);
|
||||
this.dispatchToken = AppDispatcher.register(this.handleEventPayload);
|
||||
|
||||
this.fileInfo = new Map();
|
||||
this.setMaxListeners(600);
|
||||
|
||||
this.fileInfosByPost = new Map();
|
||||
}
|
||||
|
||||
addChangeListener(callback) {
|
||||
this.on(CHANGE_EVENT, callback);
|
||||
}
|
||||
|
||||
removeChangeListener(callback) {
|
||||
this.removeListener(CHANGE_EVENT, callback);
|
||||
}
|
||||
emitChange(filename) {
|
||||
this.emit(CHANGE_EVENT, filename);
|
||||
|
||||
emitChange() {
|
||||
this.emit(CHANGE_EVENT);
|
||||
}
|
||||
|
||||
hasInfo(filename) {
|
||||
return this.fileInfo.has(filename);
|
||||
hasInfosForPost(postId) {
|
||||
return this.fileInfosByPost.has(postId);
|
||||
}
|
||||
|
||||
getInfo(filename) {
|
||||
return this.fileInfo.get(filename);
|
||||
getInfosForPost(postId) {
|
||||
return this.fileInfosByPost.get(postId);
|
||||
}
|
||||
|
||||
setInfo(filename, info) {
|
||||
this.fileInfo.set(filename, info);
|
||||
saveInfos(postId, infos) {
|
||||
this.fileInfosByPost.set(postId, infos);
|
||||
}
|
||||
|
||||
getFileUrl(fileId) {
|
||||
return `/api/v3/files/${fileId}/get`;
|
||||
}
|
||||
|
||||
getFileThumbnailUrl(fileId) {
|
||||
return `/api/v3/files/${fileId}/get_thumbnail`;
|
||||
}
|
||||
|
||||
getFilePreviewUrl(fileId) {
|
||||
return `/api/v3/files/${fileId}/get_preview`;
|
||||
}
|
||||
|
||||
handleEventPayload(payload) {
|
||||
const action = payload.action;
|
||||
|
||||
switch (action.type) {
|
||||
case ActionTypes.RECEIVED_FILE_INFO:
|
||||
this.setInfo(action.filename, action.info);
|
||||
this.emitChange(action.filename);
|
||||
case ActionTypes.RECEIVED_FILE_INFOS:
|
||||
// This assumes that all received file infos are for a single post
|
||||
this.saveInfos(action.postId, action.infos);
|
||||
this.emitChange(action.postId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new FileStore();
|
||||
export default new FileStore();
|
||||
@@ -84,7 +84,7 @@ class NotificationStoreClass extends EventEmitter {
|
||||
if (msgProps.image) {
|
||||
body = username + Utils.localizeMessage('channel_loader.uploadedImage', ' uploaded an image');
|
||||
} else if (msgProps.otherFile) {
|
||||
body = Utils.localizeMessage('channel_loader.uploadedFile', ' uploaded a file');
|
||||
body = username + Utils.localizeMessage('channel_loader.uploadedFile', ' uploaded a file');
|
||||
} else {
|
||||
body = username + Utils.localizeMessage('channel_loader.something', ' did something new');
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ class PostStoreClass extends EventEmitter {
|
||||
} else if (combinedPosts.posts.hasOwnProperty(pid)) {
|
||||
combinedPosts.posts[pid] = Object.assign({}, np, {
|
||||
state: Constants.POST_DELETED,
|
||||
filenames: []
|
||||
fileIds: []
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -318,7 +318,7 @@ class PostStoreClass extends EventEmitter {
|
||||
// make sure to copy the post so that component state changes work properly
|
||||
postList.posts[post.id] = Object.assign({}, post, {
|
||||
state: Constants.POST_DELETED,
|
||||
filenames: []
|
||||
fileIds: []
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -514,7 +514,7 @@ class PostStoreClass extends EventEmitter {
|
||||
}
|
||||
|
||||
getEmptyDraft() {
|
||||
return {message: '', uploadsInProgress: [], previews: []};
|
||||
return {message: '', uploadsInProgress: [], fileInfos: []};
|
||||
}
|
||||
|
||||
storeCurrentDraft(draft) {
|
||||
|
||||
248
webapp/tests/client_file.test.jsx
Обычный файл
248
webapp/tests/client_file.test.jsx
Обычный файл
@@ -0,0 +1,248 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import assert from 'assert';
|
||||
import TestHelper from './test_helper.jsx';
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
describe('Client.File', function() {
|
||||
this.timeout(100000);
|
||||
|
||||
before(function() {
|
||||
// write a temporary file so that we have something to upload for testing
|
||||
const buffer = new Buffer('R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=', 'base64');
|
||||
|
||||
const testGif = fs.openSync('test.gif', 'w+');
|
||||
fs.writeFileSync(testGif, buffer);
|
||||
});
|
||||
|
||||
after(function() {
|
||||
fs.unlinkSync('test.gif');
|
||||
});
|
||||
|
||||
it('uploadFile', function(done) {
|
||||
TestHelper.initBasic(() => {
|
||||
const clientId = TestHelper.generateId();
|
||||
|
||||
TestHelper.basicClient().uploadFile(
|
||||
fs.createReadStream('test.gif'),
|
||||
'test.gif',
|
||||
TestHelper.basicChannel().id,
|
||||
clientId,
|
||||
function(resp) {
|
||||
assert.equal(resp.file_infos.length, 1);
|
||||
assert.equal(resp.client_ids.length, 1);
|
||||
assert.equal(resp.client_ids[0], clientId);
|
||||
|
||||
done();
|
||||
},
|
||||
function(err) {
|
||||
done(new Error(err.message));
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('getFile', function(done) {
|
||||
TestHelper.initBasic(() => {
|
||||
TestHelper.basicClient().uploadFile(
|
||||
fs.createReadStream('test.gif'),
|
||||
'test.gif',
|
||||
TestHelper.basicChannel().id,
|
||||
'',
|
||||
function(resp) {
|
||||
TestHelper.basicClient().getFile(
|
||||
resp.file_infos[0].id,
|
||||
function() {
|
||||
done();
|
||||
},
|
||||
function(err2) {
|
||||
done(new Error(err2.message));
|
||||
}
|
||||
);
|
||||
},
|
||||
function(err) {
|
||||
done(new Error(err.message));
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('getFileThumbnail', function(done) {
|
||||
TestHelper.initBasic(() => {
|
||||
TestHelper.basicClient().uploadFile(
|
||||
fs.createReadStream('test.gif'),
|
||||
'test.gif',
|
||||
TestHelper.basicChannel().id,
|
||||
'',
|
||||
function(resp) {
|
||||
TestHelper.basicClient().getFileThumbnail(
|
||||
resp.file_infos[0].id,
|
||||
function() {
|
||||
done();
|
||||
},
|
||||
function(err) {
|
||||
done(new Error(err.message));
|
||||
}
|
||||
);
|
||||
},
|
||||
function(err) {
|
||||
done(new Error(err.message));
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('getFilePreview', function(done) {
|
||||
TestHelper.initBasic(() => {
|
||||
TestHelper.basicClient().uploadFile(
|
||||
fs.createReadStream('test.gif'),
|
||||
'test.gif',
|
||||
TestHelper.basicChannel().id,
|
||||
'',
|
||||
function(resp) {
|
||||
TestHelper.basicClient().getFilePreview(
|
||||
resp.file_infos[0].id,
|
||||
function() {
|
||||
done();
|
||||
},
|
||||
function(err2) {
|
||||
done(new Error(err2.message));
|
||||
}
|
||||
);
|
||||
},
|
||||
function(err) {
|
||||
done(new Error(err.message));
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('getFileInfo', function(done) {
|
||||
TestHelper.initBasic(() => {
|
||||
TestHelper.basicClient().uploadFile(
|
||||
fs.createReadStream('test.gif'),
|
||||
'test.gif',
|
||||
TestHelper.basicChannel().id,
|
||||
'',
|
||||
function(resp) {
|
||||
const fileId = resp.file_infos[0].id;
|
||||
|
||||
TestHelper.basicClient().getFileInfo(
|
||||
fileId,
|
||||
function(info) {
|
||||
assert.equal(info.id, fileId);
|
||||
assert.equal(info.name, 'test.gif');
|
||||
|
||||
done();
|
||||
},
|
||||
function(err2) {
|
||||
done(new Error(err2.message));
|
||||
}
|
||||
);
|
||||
},
|
||||
function(err) {
|
||||
done(new Error(err.message));
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('getPublicLink', function(done) {
|
||||
TestHelper.initBasic(() => {
|
||||
TestHelper.basicClient().uploadFile(
|
||||
fs.createReadStream('test.gif'),
|
||||
'test.gif',
|
||||
TestHelper.basicChannel().id,
|
||||
'',
|
||||
function(resp) {
|
||||
const post = TestHelper.fakePost();
|
||||
post.channel_id = TestHelper.basicChannel().id;
|
||||
post.file_ids = resp.file_infos.map((info) => info.id);
|
||||
|
||||
TestHelper.basicClient().createPost(
|
||||
post,
|
||||
function(data) {
|
||||
assert.deepEqual(data.file_ids, post.file_ids);
|
||||
|
||||
TestHelper.basicClient().getPublicLink(
|
||||
post.file_ids[0],
|
||||
function() {
|
||||
done(new Error('public links should be disabled by default'));
|
||||
|
||||
// request.
|
||||
// get(link).
|
||||
// end(TestHelper.basicChannel().handleResponse.bind(
|
||||
// this,
|
||||
// 'getPublicLink',
|
||||
// function() {
|
||||
// done();
|
||||
// },
|
||||
// function(err4) {
|
||||
// done(new Error(err4.message));
|
||||
// }
|
||||
// ));
|
||||
},
|
||||
function() {
|
||||
done();
|
||||
|
||||
// done(new Error(err3.message));
|
||||
}
|
||||
);
|
||||
},
|
||||
function(err2) {
|
||||
done(new Error(err2.message));
|
||||
}
|
||||
);
|
||||
},
|
||||
function(err) {
|
||||
done(new Error(err.message));
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('getFileInfosForPost', function(done) {
|
||||
TestHelper.initBasic(() => {
|
||||
TestHelper.basicClient().uploadFile(
|
||||
fs.createReadStream('test.gif'),
|
||||
'test.gif',
|
||||
TestHelper.basicChannel().id,
|
||||
'',
|
||||
function(resp) {
|
||||
const post = TestHelper.fakePost();
|
||||
post.channel_id = TestHelper.basicChannel().id;
|
||||
post.file_ids = resp.file_infos.map((info) => info.id);
|
||||
|
||||
TestHelper.basicClient().createPost(
|
||||
post,
|
||||
function(data) {
|
||||
assert.deepEqual(data.file_ids, post.file_ids);
|
||||
|
||||
TestHelper.basicClient().getFileInfosForPost(
|
||||
post.channel_id,
|
||||
data.id,
|
||||
function(files) {
|
||||
assert.equal(files.length, 1);
|
||||
assert.equal(files[0].id, resp.file_infos[0].id);
|
||||
|
||||
done();
|
||||
},
|
||||
function(err3) {
|
||||
done(new Error(err3.message));
|
||||
}
|
||||
);
|
||||
},
|
||||
function(err2) {
|
||||
done(new Error(err2.message));
|
||||
}
|
||||
);
|
||||
},
|
||||
function(err) {
|
||||
done(new Error(err.message));
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -43,43 +43,5 @@ describe('Client.General', function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('File.getFileInfo', function(done) {
|
||||
TestHelper.initBasic(() => {
|
||||
TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
|
||||
|
||||
TestHelper.basicClient().getFileInfo(
|
||||
`/${TestHelper.basicChannel().id}/${TestHelper.basicUser().id}/filename.txt`,
|
||||
function(data) {
|
||||
assert.equal(data.filename, 'filename.txt');
|
||||
done();
|
||||
},
|
||||
function(err) {
|
||||
done(new Error(err.message));
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('File.getPublicLink', function(done) {
|
||||
TestHelper.initBasic(() => {
|
||||
TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
|
||||
var data = {};
|
||||
data.channel_id = TestHelper.basicChannel().id;
|
||||
data.user_id = TestHelper.basicUser().id;
|
||||
data.filename = `/${TestHelper.basicChannel().id}/${TestHelper.basicUser().id}/filename.txt`;
|
||||
|
||||
TestHelper.basicClient().getPublicLink(
|
||||
data,
|
||||
function() {
|
||||
done(new Error('not enabled'));
|
||||
},
|
||||
function(err) {
|
||||
assert.equal(err.id, 'api.file.get_public_link.disabled.app_error');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -230,5 +230,7 @@ describe('Client.Posts', function() {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// getFileInfosForPost is tested in client_files.test.jsx
|
||||
});
|
||||
|
||||
|
||||
@@ -715,6 +715,32 @@ export function getPostsAfter(postId, offset, numPost, isPost) {
|
||||
);
|
||||
}
|
||||
|
||||
export function getFileInfosForPost(channelId, postId) {
|
||||
const callName = 'getFileInfosForPost' + postId;
|
||||
|
||||
if (isCallInProgress(callName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Client.getFileInfosForPost(
|
||||
channelId,
|
||||
postId,
|
||||
(data) => {
|
||||
callTracker[callName] = 0;
|
||||
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_FILE_INFOS,
|
||||
postId,
|
||||
infos: data
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
callTracker[callName] = 0;
|
||||
dispatchError(err, 'getPostFile');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function getMe() {
|
||||
if (isCallInProgress('getMe')) {
|
||||
return null;
|
||||
@@ -923,34 +949,6 @@ export function getSuggestedCommands(command, suggestionId, component) {
|
||||
);
|
||||
}
|
||||
|
||||
export function getFileInfo(filename) {
|
||||
const callName = 'getFileInfo' + filename;
|
||||
|
||||
if (isCallInProgress(callName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
callTracker[callName] = utils.getTimestamp();
|
||||
|
||||
Client.getFileInfo(
|
||||
filename,
|
||||
(data) => {
|
||||
callTracker[callName] = 0;
|
||||
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_FILE_INFO,
|
||||
filename,
|
||||
info: data
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
callTracker[callName] = 0;
|
||||
|
||||
dispatchError(err, 'getFileInfo');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function getStandardAnalytics(teamId) {
|
||||
const callName = 'getStandardAnaytics' + teamId;
|
||||
|
||||
@@ -1432,8 +1430,8 @@ export function regenCommandToken(id) {
|
||||
);
|
||||
}
|
||||
|
||||
export function getPublicLink(filename, success, error) {
|
||||
const callName = 'getPublicLink' + filename;
|
||||
export function getPublicLink(fileId, success, error) {
|
||||
const callName = 'getPublicLink' + fileId;
|
||||
|
||||
if (isCallInProgress(callName)) {
|
||||
return;
|
||||
@@ -1442,7 +1440,7 @@ export function getPublicLink(filename, success, error) {
|
||||
callTracker[callName] = utils.getTimestamp();
|
||||
|
||||
Client.getPublicLink(
|
||||
filename,
|
||||
fileId,
|
||||
(link) => {
|
||||
callTracker[callName] = 0;
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ export const ActionTypes = keyMirror({
|
||||
RECEIVED_PREFERENCE: null,
|
||||
RECEIVED_PREFERENCES: null,
|
||||
DELETED_PREFERENCES: null,
|
||||
RECEIVED_FILE_INFO: null,
|
||||
RECEIVED_FILE_INFOS: null,
|
||||
RECEIVED_ANALYTICS: null,
|
||||
|
||||
RECEIVED_INCOMING_WEBHOOKS: null,
|
||||
|
||||
@@ -136,14 +136,9 @@ export function highlight(lang, code) {
|
||||
return TextFormatting.sanitizeHtml(code);
|
||||
}
|
||||
|
||||
export function getLanguageFromFilename(filename) {
|
||||
const fileSplit = filename.split('.');
|
||||
|
||||
let ext = fileSplit.length > 1 ? fileSplit[fileSplit.length - 1] : '';
|
||||
ext = ext.toLowerCase();
|
||||
|
||||
export function getLanguageFromFileExtension(extension) {
|
||||
for (var key in HighlightedLanguages) {
|
||||
if (HighlightedLanguages[key].extensions.find((x) => x === ext)) {
|
||||
if (HighlightedLanguages[key].extensions.find((x) => x === extension)) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,8 +413,8 @@ export function getFileType(extin) {
|
||||
return 'other';
|
||||
}
|
||||
|
||||
export function getPreviewImagePathForFileType(fileTypeIn) {
|
||||
var fileType = fileTypeIn.toLowerCase();
|
||||
export function getFileIconPath(fileInfo) {
|
||||
const fileType = getFileType(fileInfo.extension);
|
||||
|
||||
var icon;
|
||||
if (fileType in Constants.ICON_FROM_TYPE) {
|
||||
@@ -451,19 +451,6 @@ export function splitFileLocation(fileLocation) {
|
||||
return {ext, name: filename, path: filePath};
|
||||
}
|
||||
|
||||
export function getPreviewImagePath(filename) {
|
||||
// Returns the path to a preview image that can be used to represent a file.
|
||||
const fileInfo = splitFileLocation(filename);
|
||||
const fileType = getFileType(fileInfo.ext);
|
||||
|
||||
if (fileType === 'image') {
|
||||
return getFileUrl(fileInfo.path + '_preview.jpg');
|
||||
}
|
||||
|
||||
// only images have proper previews, so just use a placeholder icon for non-images
|
||||
return getPreviewImagePathForFileType(fileType);
|
||||
}
|
||||
|
||||
export function toTitleCase(str) {
|
||||
function doTitleCase(txt) {
|
||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||
|
||||
Ссылка в новой задаче
Block a user