From eddb79d97d72a63fc3af6741567077e3e59b2871 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Wed, 22 Jul 2015 17:18:33 -0400 Subject: Add an info box next to image thumbnails which provides the name, type, and size of a file. --- web/react/components/post_body.jsx | 51 ++++++++++++++++++++++++-------- web/react/utils/utils.jsx | 33 +++++++++++++++++++++ web/sass-files/sass/partials/_files.scss | 32 ++++++++++++++++---- 3 files changed, 98 insertions(+), 18 deletions(-) (limited to 'web') diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx index 641ffeef2..29687b94e 100644 --- a/web/react/components/post_body.jsx +++ b/web/react/components/post_body.jsx @@ -114,23 +114,48 @@ module.exports = React.createClass({ } fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path; + var thumbnail; if (type === "image") { - if (i < Constants.MAX_DISPLAY_FILES) { - postFiles.push( -
-
+ thumbnail = ( + +
+ + ); + } else { + thumbnail = ( + +
+ + ); + } + + var containerClassName = "post-image__column"; + if (type !== "image") { + containerClassName += " custom-file"; + } + + postFiles.push( +
+ {thumbnail} +
+
{fileInfo.name}
+
+ {fileInfo.ext.toUpperCase()} +
- ); +
+
+ ); + + // asynchronously request the size of the file so that we can display it next to the thumbnail + utils.getFileSize(fileInfo.path + "." + fileInfo.ext, function(self, filename) { + return function(size) { + self.refs[filename + "__size"].getDOMNode().innerHTML = " " + utils.fileSizeToString(size); } + }(this, filenames[i])); + + if (type === "image") { images.push(filenames[i]); - } else if (i < Constants.MAX_DISPLAY_FILES) { - postFiles.push( -
- -
- -
- ); } } } diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 8a4d92b85..aa59201cb 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -557,6 +557,23 @@ module.exports.splitFileLocation = function(fileLocation) { return {'ext': ext, 'name': filename, 'path': filePath}; } +// Asynchronously gets the size of a file by requesting its headers. If successful, it calls the +// provided callback with the file size in bytes as the argument. +module.exports.getFileSize = function(url, callback) { + var request = new XMLHttpRequest(); + + request.open('HEAD', url, true); + request.onreadystatechange = function() { + if (request.readyState == 4 && request.status == 200) { + if (callback) { + callback(parseInt(request.getResponseHeader("content-length"))); + } + } + }; + + request.send(); +}; + module.exports.toTitleCase = function(str) { return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();}); } @@ -847,4 +864,20 @@ module.exports.getWindowLocationOrigin = function() { windowLocationOrigin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: ''); } return windowLocationOrigin; +} + +// Converts a file size in bytes into a human-readable string of the form "123MB". +module.exports.fileSizeToString = function(bytes) { + // it's unlikely that we'll have files bigger than this + if (bytes > 1024 * 1024 * 1024 * 1024) { + return Math.floor(bytes / (1024 * 1024 * 1024 * 1024)) + "TB"; + } else if (bytes > 1024 * 1024 * 1024) { + return Math.floor(bytes / (1024 * 1024 * 1024)) + "GB"; + } else if (bytes > 1024 * 1024) { + return Math.floor(bytes / (1024 * 1024)) + "MB"; + } else if (bytes > 1024) { + return Math.floor(bytes / 1024) + "KB"; + } else { + return bytes + "B"; + } }; diff --git a/web/sass-files/sass/partials/_files.scss b/web/sass-files/sass/partials/_files.scss index 56d03e171..2a4b21d01 100644 --- a/web/sass-files/sass/partials/_files.scss +++ b/web/sass-files/sass/partials/_files.scss @@ -117,13 +117,14 @@ } .post-image__column { position: relative; - width: 120px; + width: 240px; height: 100px; float: left; margin: 5px 10px 5px 0; + display: flex; + border: 1px solid lightgrey; &.custom-file { - width: 85px; - height: 100px; + // TODO remove this class if it doesn't end up getting used any more } .post__load { height: 100%; @@ -133,13 +134,34 @@ background-position: center; } .post__image { - height: 100%; width: 100%; - border: 1px solid #E2E2E2; + height: 100%; background-color: #FFF; background-repeat: no-repeat; background-position: top left; } + .post-image__thumbnail { + width: 50%; + height: 100%; + } + .post-image__details { + width: 50%; + height: 100%; + background: white; + border-left: 1px solid lightgrey; + font-size: small; + padding: 5px 5px; + .post-image__name { + font-weight: 600; + } + .post-image__type { + color: grey; + } + .post-image__size { + border-left: 2px; + color: grey; + } + } a { text-decoration: none; color: grey; -- cgit v1.2.3-1-g7c22 From 5488b5e2164518d9b2a745a9906152a4e6c0be10 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Thu, 23 Jul 2015 16:45:16 -0400 Subject: Reconcile the code to display attachment previews into a FileAttachmentList class to clean up duplicated code. --- web/react/components/file_attachment.jsx | 109 +++++++++++++++++++++ web/react/components/file_attachment_list.jsx | 47 +++++++++ web/react/components/post_body.jsx | 123 ++---------------------- web/react/components/post_right.jsx | 131 +++----------------------- 4 files changed, 176 insertions(+), 234 deletions(-) create mode 100644 web/react/components/file_attachment.jsx create mode 100644 web/react/components/file_attachment_list.jsx (limited to 'web') diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx new file mode 100644 index 000000000..4a81dd8f3 --- /dev/null +++ b/web/react/components/file_attachment.jsx @@ -0,0 +1,109 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var utils = require('../utils/utils.jsx'); + +module.exports = React.createClass({ + displayName: "FileAttachment", + propTypes: { + filenames: React.PropTypes.arrayOf(React.PropTypes.string).isRequired, + index: React.PropTypes.number.isRequired, + imageModalId: React.PropTypes.string.isRequired, + handleImageClick: React.PropTypes.func + }, + componentDidMount: function() { + var filename = this.props.filenames[this.props.index]; + + var self = this; + + if (filename) { + var fileInfo = utils.splitFileLocation(filename); + if (Object.keys(fileInfo).length === 0) return; + + var type = utils.getFileType(fileInfo.ext); + + // This is a temporary patch to fix issue with old files using absolute paths + if (fileInfo.path.indexOf("/api/v1/files/get") != -1) { + fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; + } + fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path; + + if (type === "image") { + $('').attr('src', fileInfo.path+'_thumb.jpg').load(function(path, name){ return function() { + $(this).remove(); + if (name in self.refs) { + var imgDiv = self.refs[name].getDOMNode(); + + $(imgDiv).removeClass('post__load'); + $(imgDiv).addClass('post__image'); + + var re1 = new RegExp(' ', 'g'); + var re2 = new RegExp('\\(', 'g'); + var re3 = new RegExp('\\)', 'g'); + var url = path.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29'); + $(imgDiv).css('background-image', 'url('+url+'_thumb.jpg)'); + } + }}(fileInfo.path, filename)); + } + } + }, + render: function() { + var filenames = this.props.filenames; + var filename = filenames[this.props.index]; + + var fileInfo = utils.splitFileLocation(filename); + if (Object.keys(fileInfo).length === 0) return null; + + var type = utils.getFileType(fileInfo.ext); + + // This is a temporary patch to fix issue with old files using absolute paths + if (fileInfo.path.indexOf("/api/v1/files/get") != -1) { + fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; + } + fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path; + + var thumbnail; + if (type === "image") { + thumbnail = ( + +
+ + ); + } else { + thumbnail = ( + +
+ + ); + } + + var containerClassName = "post-image__column"; + if (type !== "image") { + containerClassName += " custom-file"; + } + + // TODO fix the race condition here where the file size may arrive before the rest of the page is rendered + // asynchronously request the size of the file so that we can display it next to the thumbnail + utils.getFileSize(fileInfo.path + "." + fileInfo.ext, function(self, _filename) { + return function(size) { + if ((_filename + "__size") in self.refs) { + self.refs[_filename + "__size"].getDOMNode().innerHTML = " " + utils.fileSizeToString(size); + } + } + }(this, filename)); + + return ( +
+ {thumbnail} +
+
{fileInfo.name}
+
+ {fileInfo.ext.toUpperCase()} + +
+
+
+ ); + } +}); diff --git a/web/react/components/file_attachment_list.jsx b/web/react/components/file_attachment_list.jsx new file mode 100644 index 000000000..0b52d6a70 --- /dev/null +++ b/web/react/components/file_attachment_list.jsx @@ -0,0 +1,47 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var ViewImageModal = require('./view_image.jsx'); +var FileAttachment = require('./file_attachment.jsx'); +var Constants = require('../utils/constants.jsx'); + +module.exports = React.createClass({ + displayName: "FileAttachmentList", + propTypes: { + filenames: React.PropTypes.arrayOf(React.PropTypes.string).isRequired, + postId: React.PropTypes.string.isRequired, + channelId: React.PropTypes.string, + userId: React.PropTypes.string + }, + getInitialState: function() { + return {startImgId: 0}; + }, + render: function() { + var filenames = this.props.filenames; + + var postImageModalId = "view_image_modal_" + this.props.postId; + + var postFiles = []; + for (var i = 0; i < filenames.length && i < Constants.MAX_DISPLAY_FILES; i++) { + postFiles.push(); + } + + return ( +
+
+ {postFiles} +
+ +
+ ); + }, + handleImageClick: function(e) { + this.setState({startImgId: parseInt($(e.target.parentNode).attr('data-img-id'))}); + } +}); diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx index 29687b94e..d29784db9 100644 --- a/web/react/components/post_body.jsx +++ b/web/react/components/post_body.jsx @@ -1,63 +1,23 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var CreateComment = require( './create_comment.jsx' ); +var FileAttachmentList = require('./file_attachment_list.jsx'); var UserStore = require('../stores/user_store.jsx'); var utils = require('../utils/utils.jsx'); -var ViewImageModal = require('./view_image.jsx'); -var Constants = require('../utils/constants.jsx'); module.exports = React.createClass({ - handleImageClick: function(e) { - this.setState({startImgId: parseInt($(e.target.parentNode).attr('data-img-id'))}); - }, componentWillReceiveProps: function(nextProps) { var linkData = utils.extractLinks(nextProps.post.message); this.setState({ links: linkData["links"], message: linkData["text"] }); }, - componentDidMount: function() { - var filenames = this.props.post.filenames; - var self = this; - if (filenames) { - var re1 = new RegExp(' ', 'g'); - var re2 = new RegExp('\\(', 'g'); - var re3 = new RegExp('\\)', 'g'); - for (var i = 0; i < filenames.length && i < Constants.MAX_DISPLAY_FILES; i++) { - var fileInfo = utils.splitFileLocation(filenames[i]); - if (Object.keys(fileInfo).length === 0) continue; - - var type = utils.getFileType(fileInfo.ext); - - // This is a temporary patch to fix issue with old files using absolute paths - if (fileInfo.path.indexOf("/api/v1/files/get") != -1) { - fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; - } - fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path; - - if (type === "image") { - $('').attr('src', fileInfo.path+'_thumb.jpg').load(function(path, name){ return function() { - $(this).remove(); - if (name in self.refs) { - var imgDiv = self.refs[name].getDOMNode(); - $(imgDiv).removeClass('post__load'); - $(imgDiv).addClass('post__image'); - var url = path.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29'); - $(imgDiv).css('background-image', 'url('+url+'_thumb.jpg)'); - } - }}(fileInfo.path, filenames[i])); - } - } - } - }, getInitialState: function() { var linkData = utils.extractLinks(this.props.post.message); - return { startImgId: 0, links: linkData["links"], message: linkData["text"] }; + return { links: linkData["links"], message: linkData["text"] }; }, render: function() { var post = this.props.post; var filenames = this.props.post.filenames; var parentPost = this.props.parentPost; - var postImageModalId = "view_image_modal_" + post.id; var inner = utils.textToJsx(this.state.message); var comment = ""; @@ -99,69 +59,8 @@ module.exports = React.createClass({ postClass += " post-comment"; } - var postFiles = []; - var images = []; - if (filenames) { - for (var i = 0; i < filenames.length; i++) { - var fileInfo = utils.splitFileLocation(filenames[i]); - if (Object.keys(fileInfo).length === 0) continue; - - var type = utils.getFileType(fileInfo.ext); - - // This is a temporary patch to fix issue with old files using absolute paths - if (fileInfo.path.indexOf("/api/v1/files/get") != -1) { - fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; - } - fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path; - - var thumbnail; - if (type === "image") { - thumbnail = ( - -
- - ); - } else { - thumbnail = ( - -
- - ); - } - - var containerClassName = "post-image__column"; - if (type !== "image") { - containerClassName += " custom-file"; - } - - postFiles.push( -
- {thumbnail} -
-
{fileInfo.name}
-
- {fileInfo.ext.toUpperCase()} - -
-
-
- ); - - // asynchronously request the size of the file so that we can display it next to the thumbnail - utils.getFileSize(fileInfo.path + "." + fileInfo.ext, function(self, filename) { - return function(size) { - self.refs[filename + "__size"].getDOMNode().innerHTML = " " + utils.fileSizeToString(size); - } - }(this, filenames[i])); - - if (type === "image") { - images.push(filenames[i]); - } - } - } - var embed; - if (postFiles.length === 0 && this.state.links) { + if (filenames.length === 0 && this.state.links) { embed = utils.getEmbed(this.state.links[0]); } @@ -170,21 +69,13 @@ module.exports = React.createClass({ { comment }

{inner}

{ filenames && filenames.length > 0 ? -
- { postFiles } -
- : "" } - { embed } - - { images.length > 0 ? - + filenames={filenames} /> : "" } + { embed }
); } diff --git a/web/react/components/post_right.jsx b/web/react/components/post_right.jsx index 8097a181e..861f03673 100644 --- a/web/react/components/post_right.jsx +++ b/web/react/components/post_right.jsx @@ -10,7 +10,7 @@ var utils = require('../utils/utils.jsx'); var SearchBox =require('./search_bar.jsx'); var CreateComment = require( './create_comment.jsx' ); var Constants = require('../utils/constants.jsx'); -var ViewImageModal = require('./view_image.jsx'); +var FileAttachmentList = require('./file_attachment_list.jsx'); var ActionTypes = Constants.ActionTypes; RhsHeaderPost = React.createClass({ @@ -55,17 +55,8 @@ RhsHeaderPost = React.createClass({ }); RootPost = React.createClass({ - handleImageClick: function(e) { - this.setState({startImgId: parseInt($(e.target.parentNode).attr('data-img-id'))}); - }, - getInitialState: function() { - return { startImgId: 0 }; - }, render: function() { - - var postImageModalId = "rhs_view_image_modal_" + this.props.post.id; var message = utils.textToJsx(this.props.post.message); - var filenames = this.props.post.filenames; var isOwner = UserStore.getCurrentId() == this.props.post.user_id; var timestamp = UserStore.getProfile(this.props.post.user_id).update_at; var channel = ChannelStore.get(this.props.post.channel_id); @@ -84,42 +75,6 @@ RootPost = React.createClass({ channelName = (channel.type === 'D') ? "Private Message" : channel.display_name; } - if (filenames) { - var postFiles = []; - var images = []; - var re1 = new RegExp(' ', 'g'); - var re2 = new RegExp('\\(', 'g'); - var re3 = new RegExp('\\)', 'g'); - for (var i = 0; i < filenames.length && i < Constants.MAX_DISPLAY_FILES; i++) { - var fileInfo = utils.splitFileLocation(filenames[i]); - var ftype = utils.getFileType(fileInfo.ext); - - // This is a temporary patch to fix issue with old files using absolute paths - if (fileInfo.path.indexOf("/api/v1/files/get") != -1) { - fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; - } - fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path; - - if (ftype === "image") { - var url = fileInfo.path.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29'); - postFiles.push( -
-
-
- ); - images.push(filenames[i]); - } else { - postFiles.push( -
- -
- -
- ); - } - } - } - return (
{ channelName }
@@ -146,19 +101,12 @@ RootPost = React.createClass({

{message}

- { filenames.length > 0 ? -
- { postFiles } -
- : "" } - { images.length > 0 ? - + { this.props.post.filenames && this.props.post.filenames.length > 0 ? + : "" }
@@ -169,14 +117,7 @@ RootPost = React.createClass({ }); CommentPost = React.createClass({ - handleImageClick: function(e) { - this.setState({startImgId: parseInt($(e.target.parentNode).attr('data-img-id'))}); - }, - getInitialState: function() { - return { startImgId: 0 }; - }, render: function() { - var commentClass = "post"; var currentUserCss = ""; @@ -184,8 +125,6 @@ CommentPost = React.createClass({ currentUserCss = "current--user"; } - var postImageModalId = "rhs_comment_view_image_modal_" + this.props.post.id; - var filenames = this.props.post.filenames; var isOwner = UserStore.getCurrentId() == this.props.post.user_id; var type = "Post" @@ -193,43 +132,6 @@ CommentPost = React.createClass({ type = "Comment" } - if (filenames) { - var postFiles = []; - var images = []; - var re1 = new RegExp(' ', 'g'); - var re2 = new RegExp('\\(', 'g'); - var re3 = new RegExp('\\)', 'g'); - for (var i = 0; i < filenames.length && i < Constants.MAX_DISPLAY_FILES; i++) { - - var fileInfo = utils.splitFileLocation(filenames[i]); - var type = utils.getFileType(fileInfo.ext); - - // This is a temporary patch to fix issue with old files using absolute paths - if (fileInfo.path.indexOf("/api/v1/files/get") != -1) { - fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; - } - fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path; - - if (type === "image") { - var url = fileInfo.path.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29'); - postFiles.push( -
-
-
- ); - images.push(filenames[i]); - } else { - postFiles.push( -
- -
- -
- ); - } - } - } - var message = utils.textToJsx(this.props.post.message); var timestamp = UserStore.getCurrentUser().update_at; @@ -256,19 +158,12 @@ CommentPost = React.createClass({

{message}

- { filenames.length > 0 ? -
- { postFiles } -
- : "" } - { images.length > 0 ? - + { this.props.post.filenames && this.props.post.filenames.length > 0 ? + : "" }
-- cgit v1.2.3-1-g7c22 From 00a17e34b365c1381370a8d0f0ba9437af4f10ef Mon Sep 17 00:00:00 2001 From: hmhealey Date: Thu, 23 Jul 2015 17:05:29 -0400 Subject: Cleaned up references to the current post in post_right.jsx --- web/react/components/post_right.jsx | 63 +++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 30 deletions(-) (limited to 'web') diff --git a/web/react/components/post_right.jsx b/web/react/components/post_right.jsx index 861f03673..1d09842f3 100644 --- a/web/react/components/post_right.jsx +++ b/web/react/components/post_right.jsx @@ -56,18 +56,19 @@ RhsHeaderPost = React.createClass({ RootPost = React.createClass({ render: function() { - var message = utils.textToJsx(this.props.post.message); - var isOwner = UserStore.getCurrentId() == this.props.post.user_id; - var timestamp = UserStore.getProfile(this.props.post.user_id).update_at; - var channel = ChannelStore.get(this.props.post.channel_id); + var post = this.props.post; + var message = utils.textToJsx(post.message); + var isOwner = UserStore.getCurrentId() == post.user_id; + var timestamp = UserStore.getProfile(post.user_id).update_at; + var channel = ChannelStore.get(post.channel_id); var type = "Post"; - if (this.props.post.root_id.length > 0) { + if (post.root_id.length > 0) { type = "Comment"; } var currentUserCss = ""; - if (UserStore.getCurrentId() === this.props.post.user_id) { + if (UserStore.getCurrentId() === post.user_id) { currentUserCss = "current--user"; } @@ -79,20 +80,20 @@ RootPost = React.createClass({
{ channelName }
- +
    -
  • -
  • +
  • +
  • { isOwner ? : "" } @@ -101,12 +102,12 @@ RootPost = React.createClass({

{message}

- { this.props.post.filenames && this.props.post.filenames.length > 0 ? + { post.filenames && post.filenames.length > 0 ? + postId={post.id} + channelId={post.channel_id} + userId={post.user_id} + filenames={post.filenames} /> : "" }
@@ -118,39 +119,41 @@ RootPost = React.createClass({ CommentPost = React.createClass({ render: function() { + var post = this.props.post; + var commentClass = "post"; var currentUserCss = ""; - if (UserStore.getCurrentId() === this.props.post.user_id) { + if (UserStore.getCurrentId() === post.user_id) { currentUserCss = "current--user"; } - var isOwner = UserStore.getCurrentId() == this.props.post.user_id; + var isOwner = UserStore.getCurrentId() == post.user_id; var type = "Post" - if (this.props.post.root_id.length > 0) { + if (post.root_id.length > 0) { type = "Comment" } - var message = utils.textToJsx(this.props.post.message); + var message = utils.textToJsx(post.message); var timestamp = UserStore.getCurrentUser().update_at; return (
- +
    -
  • -
  • +
  • +
  • { isOwner ? : "" } @@ -158,12 +161,12 @@ CommentPost = React.createClass({

{message}

- { this.props.post.filenames && this.props.post.filenames.length > 0 ? + { post.filenames && post.filenames.length > 0 ? + postId={post.id} + channelId={post.channel_id} + userId={post.user_id} + filenames={post.filenames} /> : "" }
-- cgit v1.2.3-1-g7c22 From e1257b781939bc8767b379e3e49a7f2ca7e52e7b Mon Sep 17 00:00:00 2001 From: hmhealey Date: Thu, 23 Jul 2015 18:00:57 -0400 Subject: Center image icons in attachment previews instead of stretching them --- web/sass-files/sass/partials/_files.scss | 37 +++++++++++++++----------------- 1 file changed, 17 insertions(+), 20 deletions(-) (limited to 'web') diff --git a/web/sass-files/sass/partials/_files.scss b/web/sass-files/sass/partials/_files.scss index 2a4b21d01..d61d48ae9 100644 --- a/web/sass-files/sass/partials/_files.scss +++ b/web/sass-files/sass/partials/_files.scss @@ -71,48 +71,45 @@ width:300px; height:300px; } + +@mixin file-icon($path) { + background: url($path); + background-position: center; + background-repeat: no-repeat; + @include background-size(auto 100%); +} .file-icon { width: 100%; height: 100%; &.audio { - background: url("../images/icons/audio.png"); - @include background-size(100% 100%); + @include file-icon("../images/icons/audio.png"); } &.video { - background: url("../images/icons/video.png"); - @include background-size(100% 100%); + @include file-icon("../images/icons/video.png"); } &.ppt { - background: url("../images/icons/ppt.png"); - @include background-size(100% 100%); + @include file-icon("../images/icons/ppt.png"); } &.generic { - background: url("../images/icons/generic.png"); - @include background-size(100% 100%); + @include file-icon("../images/icons/generic.png"); } &.code { - background: url("../images/icons/code.png"); - @include background-size(100% 100%); + @include file-icon("../images/icons/code.png"); } &.excel { - background: url("../images/icons/excel.png"); - @include background-size(100% 100%); + @include file-icon("../images/icons/excel.png"); } &.word { - background: url("../images/icons/word.png"); - @include background-size(100% 100%); + @include file-icon("../images/icons/word.png"); } &.pdf { - background: url("../images/icons/pdf.png"); - @include background-size(100% 100%); + @include file-icon("../images/icons/pdf.png"); } &.patch { - background: url("../images/icons/patch.png"); - @include background-size(100% 100%); + @include file-icon("../images/icons/patch.png"); } &.image { - background: url("../images/icons/image.png"); - @include background-size(100% 100%); + @include file-icon("../images/icons/image.png"); } } .post-image__column { -- cgit v1.2.3-1-g7c22 From ca2b73eac375d7037c50e96330f449de2f2381bc Mon Sep 17 00:00:00 2001 From: hmhealey Date: Thu, 23 Jul 2015 18:28:31 -0400 Subject: Fix ViewImageModals created by RHS posts so that their ids are distinct from those in the main window --- web/react/components/file_attachment.jsx | 4 ++-- web/react/components/file_attachment_list.jsx | 9 ++++----- web/react/components/post_body.jsx | 6 +++--- web/react/components/post_right.jsx | 12 ++++++------ 4 files changed, 15 insertions(+), 16 deletions(-) (limited to 'web') diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx index 4a81dd8f3..87922a615 100644 --- a/web/react/components/file_attachment.jsx +++ b/web/react/components/file_attachment.jsx @@ -8,7 +8,7 @@ module.exports = React.createClass({ propTypes: { filenames: React.PropTypes.arrayOf(React.PropTypes.string).isRequired, index: React.PropTypes.number.isRequired, - imageModalId: React.PropTypes.string.isRequired, + modalId: React.PropTypes.string.isRequired, handleImageClick: React.PropTypes.func }, componentDidMount: function() { @@ -66,7 +66,7 @@ module.exports = React.createClass({ if (type === "image") { thumbnail = ( + data-img-id={this.props.index} data-toggle="modal" data-target={"#" + this.props.modalId }>
); diff --git a/web/react/components/file_attachment_list.jsx b/web/react/components/file_attachment_list.jsx index 0b52d6a70..3da1e1482 100644 --- a/web/react/components/file_attachment_list.jsx +++ b/web/react/components/file_attachment_list.jsx @@ -9,7 +9,7 @@ module.exports = React.createClass({ displayName: "FileAttachmentList", propTypes: { filenames: React.PropTypes.arrayOf(React.PropTypes.string).isRequired, - postId: React.PropTypes.string.isRequired, + modalId: React.PropTypes.string.isRequired, channelId: React.PropTypes.string, userId: React.PropTypes.string }, @@ -18,12 +18,11 @@ module.exports = React.createClass({ }, render: function() { var filenames = this.props.filenames; - - var postImageModalId = "view_image_modal_" + this.props.postId; + var modalId = this.props.modalId; var postFiles = []; for (var i = 0; i < filenames.length && i < Constants.MAX_DISPLAY_FILES; i++) { - postFiles.push(); + postFiles.push(); } return ( @@ -34,7 +33,7 @@ module.exports = React.createClass({ diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx index d29784db9..860c96d84 100644 --- a/web/react/components/post_body.jsx +++ b/web/react/components/post_body.jsx @@ -70,10 +70,10 @@ module.exports = React.createClass({

{inner}

{ filenames && filenames.length > 0 ? + userId={post.user_id} /> : "" } { embed }
diff --git a/web/react/components/post_right.jsx b/web/react/components/post_right.jsx index 1d09842f3..ad8b54012 100644 --- a/web/react/components/post_right.jsx +++ b/web/react/components/post_right.jsx @@ -104,10 +104,10 @@ RootPost = React.createClass({

{message}

{ post.filenames && post.filenames.length > 0 ? + userId={post.user_id} /> : "" }
@@ -163,10 +163,10 @@ CommentPost = React.createClass({

{message}

{ post.filenames && post.filenames.length > 0 ? + userId={post.user_id} /> : "" }
-- cgit v1.2.3-1-g7c22 From 0f0a887205da3890671fd77ca4b7ece42de8b903 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Thu, 23 Jul 2015 18:29:53 -0400 Subject: Removed unused custom-file CSS class that was being applied to non-image file attachment previews --- web/react/components/file_attachment.jsx | 7 +------ web/sass-files/sass/partials/_files.scss | 3 --- 2 files changed, 1 insertion(+), 9 deletions(-) (limited to 'web') diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx index 87922a615..3f7deed97 100644 --- a/web/react/components/file_attachment.jsx +++ b/web/react/components/file_attachment.jsx @@ -78,11 +78,6 @@ module.exports = React.createClass({ ); } - var containerClassName = "post-image__column"; - if (type !== "image") { - containerClassName += " custom-file"; - } - // TODO fix the race condition here where the file size may arrive before the rest of the page is rendered // asynchronously request the size of the file so that we can display it next to the thumbnail utils.getFileSize(fileInfo.path + "." + fileInfo.ext, function(self, _filename) { @@ -94,7 +89,7 @@ module.exports = React.createClass({ }(this, filename)); return ( -
+
{thumbnail}
{fileInfo.name}
diff --git a/web/sass-files/sass/partials/_files.scss b/web/sass-files/sass/partials/_files.scss index d61d48ae9..ab4030935 100644 --- a/web/sass-files/sass/partials/_files.scss +++ b/web/sass-files/sass/partials/_files.scss @@ -120,9 +120,6 @@ margin: 5px 10px 5px 0; display: flex; border: 1px solid lightgrey; - &.custom-file { - // TODO remove this class if it doesn't end up getting used any more - } .post__load { height: 100%; width: 100%; -- cgit v1.2.3-1-g7c22 From 92c9ec71a051e076fd1dc3985a8a1225e5389a95 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Thu, 23 Jul 2015 18:56:35 -0400 Subject: Fixed race condition which could occur while requesting the file size for the file attachment previews --- web/react/components/file_attachment.jsx | 38 +++++++++++++++++++++++--------- web/sass-files/sass/partials/_files.scss | 2 +- 2 files changed, 29 insertions(+), 11 deletions(-) (limited to 'web') diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx index 3f7deed97..ad3e2ffb8 100644 --- a/web/react/components/file_attachment.jsx +++ b/web/react/components/file_attachment.jsx @@ -11,6 +11,9 @@ module.exports = React.createClass({ modalId: React.PropTypes.string.isRequired, handleImageClick: React.PropTypes.func }, + getInitialState: function() { + return {fileSize: 0}; + }, componentDidMount: function() { var filename = this.props.filenames[this.props.index]; @@ -47,6 +50,22 @@ module.exports = React.createClass({ } } }, + shouldComponentUpdate: function(nextProps, nextState) { + // 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 + this.refs.fileSize.getDOMNode().innerHTML = utils.fileSizeToString(nextState.fileSize); + + return false; + } else { + // we can't find the element that should hold the file size so we must not have rendered yet + return true; + } + } else { + return true; + } + }, render: function() { var filenames = this.props.filenames; var filename = filenames[this.props.index]; @@ -78,15 +97,14 @@ module.exports = React.createClass({ ); } - // TODO fix the race condition here where the file size may arrive before the rest of the page is rendered - // asynchronously request the size of the file so that we can display it next to the thumbnail - utils.getFileSize(fileInfo.path + "." + fileInfo.ext, function(self, _filename) { - return function(size) { - if ((_filename + "__size") in self.refs) { - self.refs[_filename + "__size"].getDOMNode().innerHTML = " " + utils.fileSizeToString(size); - } - } - }(this, filename)); + if (!this.state.fileSize) { + var self = this; + + // asynchronously request the size of the file so that we can display it next to the thumbnail + utils.getFileSize(fileInfo.path + "." + fileInfo.ext, function(fileSize) { + self.setState({fileSize: fileSize}); + }); + } return (
@@ -95,7 +113,7 @@ module.exports = React.createClass({
{fileInfo.name}
{fileInfo.ext.toUpperCase()} - + {this.state.fileSize ? utils.fileSizeToString(this.state.fileSize) : ""}
diff --git a/web/sass-files/sass/partials/_files.scss b/web/sass-files/sass/partials/_files.scss index ab4030935..2da4afefc 100644 --- a/web/sass-files/sass/partials/_files.scss +++ b/web/sass-files/sass/partials/_files.scss @@ -152,7 +152,7 @@ color: grey; } .post-image__size { - border-left: 2px; + margin-left: 4px; color: grey; } } -- cgit v1.2.3-1-g7c22 From 6d65071ecd1b024e0872c50749bde46b6c53b683 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Fri, 24 Jul 2015 11:16:22 -0400 Subject: Removed unused imgCount property from ViewImageModal --- web/react/components/file_attachment_list.jsx | 1 - web/react/components/view_image.jsx | 32 ++++++++++----------------- 2 files changed, 12 insertions(+), 21 deletions(-) (limited to 'web') diff --git a/web/react/components/file_attachment_list.jsx b/web/react/components/file_attachment_list.jsx index 3da1e1482..5f2690fdf 100644 --- a/web/react/components/file_attachment_list.jsx +++ b/web/react/components/file_attachment_list.jsx @@ -35,7 +35,6 @@ module.exports = React.createClass({ userId={this.props.userId} modalId={modalId} startId={this.state.startImgId} - imgCount={0} filenames={filenames} />
); diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx index 7b096c629..6ba76c0f8 100644 --- a/web/react/components/view_image.jsx +++ b/web/react/components/view_image.jsx @@ -31,18 +31,13 @@ module.exports = React.createClass({ return; }; - var src = ""; - if (this.props.imgCount > 0) { - src = this.props.filenames[id]; - } else { - var fileInfo = utils.splitFileLocation(this.props.filenames[id]); - // This is a temporary patch to fix issue with old files using absolute paths - if (fileInfo.path.indexOf("/api/v1/files/get") !== -1) { - fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; - } - fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path; - src = fileInfo['path'] + '_preview.jpg'; + var fileInfo = utils.splitFileLocation(this.props.filenames[id]); + // This is a temporary patch to fix issue with old files using absolute paths + if (fileInfo.path.indexOf("/api/v1/files/get") !== -1) { + fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; } + fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path; + var src = fileInfo['path'] + '_preview.jpg'; var self = this; var img = new Image(); @@ -141,16 +136,13 @@ module.exports = React.createClass({ for (var id in this.state.images) { var info = utils.splitFileLocation(this.props.filenames[id]); var preview_filename = ""; - if (this.props.imgCount > 0) { - preview_filename = this.props.filenames[this.state.imgId]; - } else { - // This is a temporary patch to fix issue with old files using absolute paths - if (info.path.indexOf("/api/v1/files/get") !== -1) { - info.path = info.path.split("/api/v1/files/get")[1]; - } - info.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + info.path; - preview_filename = info['path'] + '_preview.jpg'; + + // This is a temporary patch to fix issue with old files using absolute paths + if (info.path.indexOf("/api/v1/files/get") !== -1) { + info.path = info.path.split("/api/v1/files/get")[1]; } + info.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + info.path; + preview_filename = info['path'] + '_preview.jpg'; var imgClass = "hidden"; if (this.state.loaded[id] && this.state.imgId == id) imgClass = ""; -- cgit v1.2.3-1-g7c22 From f502fdef5061cdd10777beeb785716b9a3a1acfd Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Sat, 25 Jul 2015 01:26:04 +0500 Subject: UI update for files --- web/sass-files/sass/partials/_files.scss | 16 ++++++---------- web/static/images/icons/audio.png | Bin 7432 -> 4859 bytes web/static/images/icons/code.png | Bin 7195 -> 4669 bytes web/static/images/icons/excel.png | Bin 6209 -> 3648 bytes web/static/images/icons/generic.png | Bin 8894 -> 6258 bytes web/static/images/icons/image.png | Bin 5604 -> 3995 bytes web/static/images/icons/patch.png | Bin 7865 -> 4956 bytes web/static/images/icons/pdf.png | Bin 11451 -> 5683 bytes web/static/images/icons/ppt.png | Bin 8450 -> 5588 bytes web/static/images/icons/video.png | Bin 5300 -> 3593 bytes web/static/images/icons/word.png | Bin 4543 -> 3674 bytes 11 files changed, 6 insertions(+), 10 deletions(-) (limited to 'web') diff --git a/web/sass-files/sass/partials/_files.scss b/web/sass-files/sass/partials/_files.scss index 2da4afefc..e8ea8817d 100644 --- a/web/sass-files/sass/partials/_files.scss +++ b/web/sass-files/sass/partials/_files.scss @@ -14,10 +14,6 @@ position: relative; border: 1px solid #DDD; @include clearfix; - &.custom-file { - width: 85px; - height: 100px; - } &:hover .remove-preview:after { @include opacity(1); } @@ -73,10 +69,10 @@ } @mixin file-icon($path) { - background: url($path); + background: #fff url($path); background-position: center; background-repeat: no-repeat; - @include background-size(auto 100%); + @include background-size(60px auto); } .file-icon { width: 100%; @@ -142,11 +138,11 @@ width: 50%; height: 100%; background: white; - border-left: 1px solid lightgrey; - font-size: small; - padding: 5px 5px; + border-left: 1px solid #ddd; + font-size: 13px; + padding: 7px; .post-image__name { - font-weight: 600; + margin-bottom: 3px; } .post-image__type { color: grey; diff --git a/web/static/images/icons/audio.png b/web/static/images/icons/audio.png index 2b6d37f8d..bd25b7f84 100644 Binary files a/web/static/images/icons/audio.png and b/web/static/images/icons/audio.png differ diff --git a/web/static/images/icons/code.png b/web/static/images/icons/code.png index 80db302ee..c59e4b8dc 100644 Binary files a/web/static/images/icons/code.png and b/web/static/images/icons/code.png differ diff --git a/web/static/images/icons/excel.png b/web/static/images/icons/excel.png index 70ddadcbf..275c65c4d 100644 Binary files a/web/static/images/icons/excel.png and b/web/static/images/icons/excel.png differ diff --git a/web/static/images/icons/generic.png b/web/static/images/icons/generic.png index d9e82c232..0eb82c2d2 100644 Binary files a/web/static/images/icons/generic.png and b/web/static/images/icons/generic.png differ diff --git a/web/static/images/icons/image.png b/web/static/images/icons/image.png index a3acdef4c..799317731 100644 Binary files a/web/static/images/icons/image.png and b/web/static/images/icons/image.png differ diff --git a/web/static/images/icons/patch.png b/web/static/images/icons/patch.png index 18af126d4..a0affc9ee 100644 Binary files a/web/static/images/icons/patch.png and b/web/static/images/icons/patch.png differ diff --git a/web/static/images/icons/pdf.png b/web/static/images/icons/pdf.png index e4582570e..8c7507a1c 100644 Binary files a/web/static/images/icons/pdf.png and b/web/static/images/icons/pdf.png differ diff --git a/web/static/images/icons/ppt.png b/web/static/images/icons/ppt.png index 3571b4649..51553a11c 100644 Binary files a/web/static/images/icons/ppt.png and b/web/static/images/icons/ppt.png differ diff --git a/web/static/images/icons/video.png b/web/static/images/icons/video.png index e61a9e5f4..f53da93e4 100644 Binary files a/web/static/images/icons/video.png and b/web/static/images/icons/video.png differ diff --git a/web/static/images/icons/word.png b/web/static/images/icons/word.png index 20f830665..658937817 100644 Binary files a/web/static/images/icons/word.png and b/web/static/images/icons/word.png differ -- cgit v1.2.3-1-g7c22 From b8a69f767c768d3ca0cebc73553a2b37e68e9347 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Fri, 24 Jul 2015 17:04:02 -0400 Subject: Added preview images for non-image files --- web/react/components/view_image.jsx | 47 +++++++++++++++++++++++-------------- web/react/utils/utils.jsx | 13 ++++++++++ 2 files changed, 42 insertions(+), 18 deletions(-) (limited to 'web') diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx index 6ba76c0f8..bd61bd9c9 100644 --- a/web/react/components/view_image.jsx +++ b/web/react/components/view_image.jsx @@ -31,17 +31,14 @@ module.exports = React.createClass({ return; }; - var fileInfo = utils.splitFileLocation(this.props.filenames[id]); - // This is a temporary patch to fix issue with old files using absolute paths - if (fileInfo.path.indexOf("/api/v1/files/get") !== -1) { - fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; - } - fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path; - var src = fileInfo['path'] + '_preview.jpg'; + var filename = this.props.filenames[id]; + + var fileInfo = utils.splitFileLocation(filename); + var fileType = utils.getFileType(fileInfo.ext); var self = this; var img = new Image(); - img.load(src, + img.load(this.getPreviewImagePath(filename), function(){ var progress = self.state.progress; progress[id] = img.completedPercentage; @@ -134,20 +131,17 @@ module.exports = React.createClass({ bgClass = "black-bg"; } else if (this.state.viewed) { for (var id in this.state.images) { - var info = utils.splitFileLocation(this.props.filenames[id]); - var preview_filename = ""; - - // This is a temporary patch to fix issue with old files using absolute paths - if (info.path.indexOf("/api/v1/files/get") !== -1) { - info.path = info.path.split("/api/v1/files/get")[1]; - } - info.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + info.path; - preview_filename = info['path'] + '_preview.jpg'; + var filename = this.props.filenames[id]; + var fileInfo = utils.splitFileLocation(filename); var imgClass = "hidden"; if (this.state.loaded[id] && this.state.imgId == id) imgClass = ""; - img[info['path']] = ; + img[fileInfo.path] = ( + + + + ); } } @@ -197,5 +191,22 @@ module.exports = React.createClass({
); + }, + getPreviewImagePath: function(filename) { + var fileInfo = utils.splitFileLocation(filename); + var fileType = utils.getFileType(fileInfo.ext); + + if (fileType === "image") { + // This is a temporary patch to fix issue with old files using absolute paths + if (fileInfo.path.indexOf("/api/v1/files/get") !== -1) { + fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; + } + fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path; + + return fileInfo.path + '_preview.jpg'; + } else { + // only images have proper previews, so just use a placeholder icon for non-images + return utils.getPreviewImagePathForFileType(fileType); + } } }); diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index aa59201cb..e48ffed9a 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -533,6 +533,19 @@ module.exports.getFileType = function(ext) { return "other"; }; +module.exports.getPreviewImagePathForFileType = function(fileType) { + fileType = fileType.toLowerCase(); + + var icon; + if (fileType in Constants.ICON_FROM_TYPE) { + icon = Constants.ICON_FROM_TYPE[fileType]; + } else { + icon = Constants.ICON_FROM_TYPE["other"]; + } + + return "/static/images/icons/" + icon + ".png"; +}; + module.exports.getIconClassName = function(fileType) { fileType = fileType.toLowerCase(); -- cgit v1.2.3-1-g7c22 From 0e121a68de2d99ac17b27e41d92efae7721c6578 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Fri, 24 Jul 2015 17:24:11 -0400 Subject: Changed the thumbnail for non-image file attachments to open the ViewImageModal on click like it does for image attachments --- web/react/components/file_attachment.jsx | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) (limited to 'web') diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx index ad3e2ffb8..fd5adb8b4 100644 --- a/web/react/components/file_attachment.jsx +++ b/web/react/components/file_attachment.jsx @@ -83,18 +83,9 @@ module.exports = React.createClass({ var thumbnail; if (type === "image") { - thumbnail = ( - -
- - ); + thumbnail =
; } else { - thumbnail = ( - -
- - ); + thumbnail =
; } if (!this.state.fileSize) { @@ -108,7 +99,10 @@ module.exports = React.createClass({ return (
- {thumbnail} + + {thumbnail} +
{fileInfo.name}
-- cgit v1.2.3-1-g7c22 From f66cd5e9773a38cca635d33d91f0ef3b056d94a0 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Tue, 28 Jul 2015 11:30:22 -0400 Subject: Added utility function to get a file attachment's url --- web/react/components/file_attachment.jsx | 10 +--------- web/react/utils/utils.jsx | 13 +++++++++++++ 2 files changed, 14 insertions(+), 9 deletions(-) (limited to 'web') diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx index fd5adb8b4..e730af5f2 100644 --- a/web/react/components/file_attachment.jsx +++ b/web/react/components/file_attachment.jsx @@ -71,16 +71,8 @@ module.exports = React.createClass({ var filename = filenames[this.props.index]; var fileInfo = utils.splitFileLocation(filename); - if (Object.keys(fileInfo).length === 0) return null; - var type = utils.getFileType(fileInfo.ext); - // This is a temporary patch to fix issue with old files using absolute paths - if (fileInfo.path.indexOf("/api/v1/files/get") != -1) { - fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; - } - fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path; - var thumbnail; if (type === "image") { thumbnail =
; @@ -92,7 +84,7 @@ module.exports = React.createClass({ var self = this; // asynchronously request the size of the file so that we can display it next to the thumbnail - utils.getFileSize(fileInfo.path + "." + fileInfo.ext, function(fileSize) { + utils.getFileSize(utils.getFileUrl(filename), function(fileSize) { self.setState({fileSize: fileSize}); }); } diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index e48ffed9a..d1513dea9 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -894,3 +894,16 @@ module.exports.fileSizeToString = function(bytes) { return bytes + "B"; } }; + +// Converts a filename (like those attached to Post objects) to a url that can be used to retrieve attachments from the server. +module.exports.getFileUrl = function(filename) { + var url = filename; + + // This is a temporary patch to fix issue with old files using absolute paths + if (url.indexOf("/api/v1/files/get") != -1) { + url = filename.split("/api/v1/files/get")[1]; + } + url = module.exports.getWindowLocationOrigin() + "/api/v1/files/get" + url; + + return url; +}; -- cgit v1.2.3-1-g7c22 From 411f8cb9effef0310abbb39f9ae53adf6ecf8aaf Mon Sep 17 00:00:00 2001 From: hmhealey Date: Tue, 28 Jul 2015 11:53:05 -0400 Subject: Fixed FileAttachment component to not spam the server with requests when displaying a zero byte file --- web/react/components/file_attachment.jsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'web') diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx index e730af5f2..346819bdd 100644 --- a/web/react/components/file_attachment.jsx +++ b/web/react/components/file_attachment.jsx @@ -12,7 +12,7 @@ module.exports = React.createClass({ handleImageClick: React.PropTypes.func }, getInitialState: function() { - return {fileSize: 0}; + return {fileSize: -1}; }, componentDidMount: function() { var filename = this.props.filenames[this.props.index]; @@ -80,7 +80,7 @@ module.exports = React.createClass({ thumbnail =
; } - if (!this.state.fileSize) { + if (this.state.fileSize < 0) { var self = this; // asynchronously request the size of the file so that we can display it next to the thumbnail @@ -89,6 +89,11 @@ module.exports = React.createClass({ }); } + var fileSizeString = ""; + if (this.state.fileSize >= 0) { + fileSizeString = utils.fileSizeToString(this.state.fileSize); + } + return (
{fileInfo.ext.toUpperCase()} - {this.state.fileSize ? utils.fileSizeToString(this.state.fileSize) : ""} + {this.fileSizeString}
-- cgit v1.2.3-1-g7c22 From b0c64c73f929c9021196706fa1352ac4aa6f95a4 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Tue, 28 Jul 2015 12:02:29 -0400 Subject: Added check to make sure that FileAttachment is mounted before asynchronously setting state to avoid warning messages. --- web/react/components/file_attachment.jsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'web') diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx index 346819bdd..385ff0e8d 100644 --- a/web/react/components/file_attachment.jsx +++ b/web/react/components/file_attachment.jsx @@ -5,6 +5,7 @@ var utils = require('../utils/utils.jsx'); module.exports = React.createClass({ displayName: "FileAttachment", + canSetState: false, propTypes: { filenames: React.PropTypes.arrayOf(React.PropTypes.string).isRequired, index: React.PropTypes.number.isRequired, @@ -15,6 +16,8 @@ module.exports = React.createClass({ return {fileSize: -1}; }, componentDidMount: function() { + this.canSetState = true; + var filename = this.props.filenames[this.props.index]; var self = this; @@ -50,6 +53,10 @@ module.exports = React.createClass({ } } }, + componentWillUnmount: function() { + // 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: function(nextProps, nextState) { // 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) { @@ -85,7 +92,9 @@ module.exports = React.createClass({ // asynchronously request the size of the file so that we can display it next to the thumbnail utils.getFileSize(utils.getFileUrl(filename), function(fileSize) { - self.setState({fileSize: fileSize}); + if (self.canSetState) { + self.setState({fileSize: fileSize}); + } }); } @@ -104,7 +113,7 @@ module.exports = React.createClass({
{fileInfo.name}
{fileInfo.ext.toUpperCase()} - {this.fileSizeString} + {fileSizeString}
-- cgit v1.2.3-1-g7c22 From af246bb44b7968b9f880d819e3aa04342846fccc Mon Sep 17 00:00:00 2001 From: hmhealey Date: Tue, 28 Jul 2015 16:41:57 -0400 Subject: Updated ViewImage modal dialog to include details about non-image files --- web/react/components/file_attachment.jsx | 2 +- web/react/components/view_image.jsx | 136 ++++++++++++++++++------------- web/react/utils/utils.jsx | 6 ++ web/sass-files/sass/partials/_files.scss | 32 ++++++++ 4 files changed, 119 insertions(+), 57 deletions(-) (limited to 'web') diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx index 385ff0e8d..57a064ff4 100644 --- a/web/react/components/file_attachment.jsx +++ b/web/react/components/file_attachment.jsx @@ -110,7 +110,7 @@ module.exports = React.createClass({ {thumbnail}
-
{fileInfo.name}
+
{decodeURIComponent(utils.getFileName(filename))}
{fileInfo.ext.toUpperCase()} {fileSizeString} diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx index bd61bd9c9..c7fb1b785 100644 --- a/web/react/components/view_image.jsx +++ b/web/react/components/view_image.jsx @@ -36,29 +36,36 @@ module.exports = React.createClass({ var fileInfo = utils.splitFileLocation(filename); var fileType = utils.getFileType(fileInfo.ext); - var self = this; - var img = new Image(); - img.load(this.getPreviewImagePath(filename), - function(){ - var progress = self.state.progress; - progress[id] = img.completedPercentage; - self.setState({ progress: progress }); - }); - img.onload = function(imgid) { - return function() { - var loaded = self.state.loaded; - loaded[imgid] = true; - self.setState({ loaded: loaded }); - $(self.refs.image.getDOMNode()).css("max-height",imgHeight); - }; - }(id); - var images = this.state.images; - images[id] = img; - this.setState({ images: images }); + if (fileType === "image") { + var self = this; + var img = new Image(); + img.load(this.getPreviewImagePath(filename), + function(){ + var progress = self.state.progress; + progress[id] = img.completedPercentage; + self.setState({ progress: progress }); + }); + img.onload = function(imgid) { + return function() { + var loaded = self.state.loaded; + loaded[imgid] = true; + self.setState({ loaded: loaded }); + $(self.refs.image.getDOMNode()).css("max-height",imgHeight); + }; + }(id); + var images = this.state.images; + images[id] = img; + this.setState({ images: images }); + } else { + // there's nothing to load for non-image files + var loaded = this.state.loaded; + loaded[id] = true; + this.setState({ loaded: loaded }); + } }, componentDidUpdate: function() { - if (this.refs.image) { - if (this.state.loaded[this.state.imgId]) { + if (this.state.loaded[this.state.imgId]) { + if (this.refs.imageWrap) { $(this.refs.imageWrap.getDOMNode()).removeClass("default"); } } @@ -104,55 +111,73 @@ module.exports = React.createClass({ loaded.push(false); progress.push(0); } - return { imgId: this.props.startId, viewed: false, loaded: loaded, progress: progress, images: {} }; + return { imgId: this.props.startId, viewed: false, loaded: loaded, progress: progress, images: {}, fileSizes: {} }; }, render: function() { if (this.props.filenames.length < 1 || this.props.filenames.length-1 < this.state.imgId) { return
; } - var fileInfo = utils.splitFileLocation(this.props.filenames[this.state.imgId]); + var filename = this.props.filenames[this.state.imgId]; + var fileUrl = utils.getFileUrl(filename); - var name = fileInfo['name'] + '.' + fileInfo['ext']; + var name = decodeURIComponent(utils.getFileName(filename)); - var loading = ""; + var content; var bgClass = ""; - var img = {}; - if (!this.state.loaded[this.state.imgId]) { + if (this.state.loaded[this.state.imgId]) { + var fileInfo = utils.splitFileLocation(filename); + var fileType = utils.getFileType(fileInfo.ext); + + if (fileType === "image") { + content = ( + + + + ); + } else { + // non-image files include a section providing details about the file + var infoString = "File type " + fileInfo.ext.toUpperCase(); + if (this.state.fileSizes[filename] && this.state.fileSizes[filename] >= 0) { + infoString += ", Size " + utils.fileSizeToString(this.state.fileSizes[filename]); + } + + content = ( +
+ + + + +
+
{name}
+
{infoString}
+
+
+ ); + + // asynchronously request the actual size of this file + if (!(filename in this.state.fileSizes)) { + var self = this; + + utils.getFileSize(utils.getFileUrl(filename), function(fileSize) { + var fileSizes = self.state.fileSizes; + fileSizes[filename] = fileSize; + self.setState(fileSizes); + }); + } + } + } else { var percentage = Math.floor(this.state.progress[this.state.imgId]); - loading = ( -
- + content = ( +
+ { percentage > 0 ? {"Previewing " + percentage + "%"} : ""}
); bgClass = "black-bg"; - } else if (this.state.viewed) { - for (var id in this.state.images) { - var filename = this.props.filenames[id]; - var fileInfo = utils.splitFileLocation(filename); - - var imgClass = "hidden"; - if (this.state.loaded[id] && this.state.imgId == id) imgClass = ""; - - img[fileInfo.path] = ( - - - - ); - } - } - - var imgFragment = React.addons.createFragment(img); - - // This is a temporary patch to fix issue with old files using absolute paths - var download_link = this.props.filenames[this.state.imgId]; - if (download_link.indexOf("/api/v1/files/get") !== -1) { - download_link = download_link.split("/api/v1/files/get")[1]; } - download_link = utils.getWindowLocationOrigin() + "/api/v1/files/get" + download_link; return ( -

+

{ server_error }

By creating an account and using Mattermost you are agreeing to our Terms of Service. If you do not agree, you cannot use this service.

+
); } diff --git a/web/react/pages/verify.jsx b/web/react/pages/verify.jsx index 69850849f..96b556983 100644 --- a/web/react/pages/verify.jsx +++ b/web/react/pages/verify.jsx @@ -3,11 +3,9 @@ var EmailVerify = require('../components/email_verify.jsx'); -global.window.setup_verify_page = function(is_verified) { - +global.window.setupVerifyPage = function setupVerifyPage(isVerified, teamURL, userEmail) { React.render( - , + , document.getElementById('verify') ); - }; diff --git a/web/templates/verify.html b/web/templates/verify.html index a61964bb3..de839db68 100644 --- a/web/templates/verify.html +++ b/web/templates/verify.html @@ -9,7 +9,7 @@
diff --git a/web/web.go b/web/web.go index 68e2a5226..b51bf256a 100644 --- a/web/web.go +++ b/web/web.go @@ -352,27 +352,26 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) { func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) { resend := r.URL.Query().Get("resend") - name := r.URL.Query().Get("name") + name := r.URL.Query().Get("teamname") email := r.URL.Query().Get("email") hashedId := r.URL.Query().Get("hid") userId := r.URL.Query().Get("uid") - if resend == "true" { - - teamId := "" - if result := <-api.Srv.Store.Team().GetByName(name); result.Err != nil { - c.Err = result.Err - return - } else { - teamId = result.Data.(*model.Team).Id - } + var team *model.Team + if result := <-api.Srv.Store.Team().GetByName(name); result.Err != nil { + c.Err = result.Err + return + } else { + team = result.Data.(*model.Team) + } - if result := <-api.Srv.Store.User().GetByEmail(teamId, email); result.Err != nil { + if resend == "true" { + if result := <-api.Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil { c.Err = result.Err return } else { user := result.Data.(*model.User) - api.FireAndForgetVerifyEmail(user.Id, strings.Split(user.Nickname, " ")[0], user.Email, name, c.GetTeamURL()) + api.FireAndForgetVerifyEmail(user.Id, user.Nickname, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team)) http.Redirect(w, r, "/", http.StatusFound) return } @@ -396,6 +395,8 @@ func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) { page := NewHtmlTemplatePage("verify", "Email Verified") page.Props["IsVerified"] = isVerified + page.Props["TeamURL"] = c.GetTeamURLFromTeam(team) + page.Props["UserEmail"] = email page.Render(c, w) } -- cgit v1.2.3-1-g7c22 From e5c79cd193f778e9036d938ba71c59d6e0cf3dc9 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Thu, 30 Jul 2015 10:54:41 -0400 Subject: makes the LHS dropdown close when clicking on the LHS header --- web/react/components/sidebar_header.jsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'web') diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx index 0156dc01a..559f2ef91 100644 --- a/web/react/components/sidebar_header.jsx +++ b/web/react/components/sidebar_header.jsx @@ -17,8 +17,15 @@ var NavbarDropdown = React.createClass({ e.preventDefault(); client.logout(); }, + blockToggle: false, componentDidMount: function() { UserStore.addTeamsChangeListener(this._onChange); + + var self = this; + $(this.refs.dropdown.getDOMNode()).on('hide.bs.dropdown', function(e) { + self.blockToggle = true; + setTimeout(function(){self.blockToggle = false;}, 100); + }); }, componentWillUnmount: function() { UserStore.removeTeamsChangeListener(this._onChange); @@ -75,7 +82,7 @@ var NavbarDropdown = React.createClass({ return (
    -
  • +
  • @@ -108,7 +115,11 @@ module.exports = React.createClass({ }, toggleDropdown: function(e) { - $('.team__header').find('.dropdown-toggle').trigger('click'); + if (this.refs.dropdown.blockToggle) { + this.refs.dropdown.blockToggle = false; + return; + } + $('.team__header').find('.dropdown-toggle').dropdown('toggle'); }, render: function() { @@ -131,7 +142,7 @@ module.exports = React.createClass({
    { this.props.teamDisplayName }
- +
); } -- cgit v1.2.3-1-g7c22 From b6018c390fc935e09ba2ba87a4ae44caaa380ba6 Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Thu, 30 Jul 2015 20:21:09 +0500 Subject: Improving smoothness of RHS search --- web/sass-files/sass/partials/_responsive.scss | 17 +++++++++++------ web/sass-files/sass/partials/_search.scss | 2 ++ 2 files changed, 13 insertions(+), 6 deletions(-) (limited to 'web') diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss index 81b94ab5a..e3f140413 100644 --- a/web/sass-files/sass/partials/_responsive.scss +++ b/web/sass-files/sass/partials/_responsive.scss @@ -427,9 +427,9 @@ body { &.white { .inner__wrap { - >.row.content { - margin-bottom: -185px; - } + >.row.content { + margin-bottom: -185px; + } } } } @@ -447,6 +447,9 @@ } } } + .search__clear { + display: block; + } .search-bar__container { padding: 0; height: 45px; @@ -457,15 +460,17 @@ @include translateX(-45px); } .search__form { - padding-left: 10px; - padding-right: 67px; + @include translateX(-45px); + padding-left: 55px; + padding-right: 24px; } .search__clear { - display: block; + @include translateX(0px); } } .search__form { border: none; + @include translateX(0px); padding: 7px 20px 0 49px; height: 45px; position: relative; diff --git a/web/sass-files/sass/partials/_search.scss b/web/sass-files/sass/partials/_search.scss index e2168ef75..9ae41ebb0 100644 --- a/web/sass-files/sass/partials/_search.scss +++ b/web/sass-files/sass/partials/_search.scss @@ -7,6 +7,8 @@ right: 0; line-height: 45px; margin-right: 13px; + @include single-transition(all, 0.2s, linear); + @include translateX(60px); z-index: 5; cursor: pointer; } -- cgit v1.2.3-1-g7c22 From 4b531ef8c4ff1ea34658f5b0b0c15ebc0321a867 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Thu, 30 Jul 2015 12:11:01 -0400 Subject: added .off() for dropdown hide event on LHS header --- web/react/components/sidebar_header.jsx | 2 ++ 1 file changed, 2 insertions(+) (limited to 'web') diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx index 559f2ef91..e01ddcd05 100644 --- a/web/react/components/sidebar_header.jsx +++ b/web/react/components/sidebar_header.jsx @@ -29,6 +29,8 @@ var NavbarDropdown = React.createClass({ }, componentWillUnmount: function() { UserStore.removeTeamsChangeListener(this._onChange); + + $(this.refs.dropdown.getDOMNode()).off('hide.bs.dropdown'); }, _onChange: function() { if (this.isMounted()) { -- cgit v1.2.3-1-g7c22 From cd2e035b25607188ae41ed2bdb90e30511ca40a8 Mon Sep 17 00:00:00 2001 From: nickago Date: Thu, 30 Jul 2015 09:57:02 -0700 Subject: Changed language in error message to reflect better English and did cosmetic refactoring --- web/react/components/signup_team_complete.jsx | 46 +++++++++++++-------------- 1 file changed, 22 insertions(+), 24 deletions(-) (limited to 'web') diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx index 83daa3b1f..fcc1f0e14 100644 --- a/web/react/components/signup_team_complete.jsx +++ b/web/react/components/signup_team_complete.jsx @@ -496,60 +496,58 @@ SendInivtesPage = React.createClass({ }); UsernamePage = React.createClass({ - submitBack: function (e) { + submitBack: function(e) { e.preventDefault(); - this.props.state.wizard = "send_invites"; + this.props.state.wizard = 'send_invites'; this.props.updateParent(this.props.state); }, - submitNext: function (e) { + submitNext: function(e) { e.preventDefault(); var name = this.refs.name.getDOMNode().value.trim(); var username_error = utils.isValidUsername(name); - if (username_error === "Cannot use a reserved word as a username.") { - this.setState({name_error: "This username is reserved, please choose a new one." }); + if (username_error === 'Cannot use a reserved word as a username.') { + this.setState({name_error: 'This username is reserved, please choose a new one.'}); return; } else if (username_error) { - this.setState({name_error: "Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'." }); + this.setState({name_error: "Username must begin with a letter, and contain between 3 to 15 characters made up of numbers, lowercase letters, and the symbols '.', '-', and '_'"}); return; } - - this.props.state.wizard = "password"; + this.props.state.wizard = 'password'; this.props.state.user.username = name; this.props.updateParent(this.props.state); }, getInitialState: function() { - return { }; + return {}; }, render: function() { - client.track('signup', 'signup_team_06_username'); - var name_error = this.state.name_error ? : null; + var name_error = this.state.name_error ? : null; return (
- -

Your username

-
{"Select a memorable username that makes it easy for " + strings.Team + "mates to identify you:"}
-
-
-
-
+ +

Your username

+
{'Select a memorable username that makes it easy for ' + strings.Team + 'mates to identify you:'}
+
+
+
+
Choose your username
- -
Usernames must begin with a letter and contain 3 to 15 characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'
+ +
Usernames must begin with a letter and contain 3 to 15 characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'
- { name_error } + {name_error}
- - -- cgit v1.2.3-1-g7c22 From 9cc369b6b637139d7ae53eb2d834ff8a6eeb439c Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Thu, 30 Jul 2015 13:42:27 -0400 Subject: Removing nickname parameter from FireAndForgetVerifyEmail --- web/web.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'web') diff --git a/web/web.go b/web/web.go index b51bf256a..8b329c149 100644 --- a/web/web.go +++ b/web/web.go @@ -371,7 +371,7 @@ func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) { return } else { user := result.Data.(*model.User) - api.FireAndForgetVerifyEmail(user.Id, user.Nickname, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team)) + api.FireAndForgetVerifyEmail(user.Id, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team)) http.Redirect(w, r, "/", http.StatusFound) return } -- cgit v1.2.3-1-g7c22 From ba06300402cf4cf6516a80873ad3351348872a56 Mon Sep 17 00:00:00 2001 From: lfbrock Date: Thu, 30 Jul 2015 14:13:34 -0400 Subject: MM-1841 Corrected help text --- web/react/components/member_list.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'web') diff --git a/web/react/components/member_list.jsx b/web/react/components/member_list.jsx index a37392f96..69da5cfc3 100644 --- a/web/react/components/member_list.jsx +++ b/web/react/components/member_list.jsx @@ -13,7 +13,7 @@ module.exports = React.createClass({ var message = ""; if (members.length === 0) - message = No users to add or manage.; + message = No users to add.; return (
-- cgit v1.2.3-1-g7c22 From e2d77c28f1226c32457891d6b6814082468548e5 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Thu, 30 Jul 2015 15:21:35 -0400 Subject: Removed ^replying in preparation for 0.6.0 --- web/react/components/create_post.jsx | 91 +-------------------------------- web/react/components/post.jsx | 2 +- web/react/components/post_list.jsx | 13 +---- web/react/stores/post_store.jsx | 17 ------ web/react/utils/constants.jsx | 1 - web/sass-files/sass/partials/_post.scss | 6 --- 6 files changed, 5 insertions(+), 125 deletions(-) (limited to 'web') diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 327520210..76286eb88 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -31,11 +31,6 @@ module.exports = React.createClass({ post.message = this.state.messageText; - // if this is a reply, trim off any carets from the beginning of a message - if (this.state.rootId && post.message[0] === "^") { - post.message = post.message.replace(/^\^+\s*/g, ""); - } - if (post.message.trim().length === 0 && this.state.previews.length === 0) { return; } @@ -73,9 +68,6 @@ module.exports = React.createClass({ post.channel_id = this.state.channel_id; post.filenames = this.state.previews; - post.root_id = this.state.rootId; - post.parent_id = this.state.parentId; - client.createPost(post, ChannelStore.getCurrent(), function(data) { PostStore.storeDraft(data.channel_id, null); @@ -92,12 +84,7 @@ module.exports = React.createClass({ }.bind(this), function(err) { var state = {} - - if (err.message === "Invalid RootId parameter") { - if ($('#post_deleted').length > 0) $('#post_deleted').modal('show'); - } else { - state.server_error = err.message; - } + state.server_error = err.message; state.submitting = false; this.setState(state); @@ -106,17 +93,6 @@ module.exports = React.createClass({ } $(".post-list-holder-by-time").perfectScrollbar('update'); - - if (this.state.rootId || this.state.parentId) { - this.setState({rootId: "", parentId: "", caretCount: 0}); - - // clear the active thread since we've now sent our message - AppDispatcher.handleViewAction({ - type: ActionTypes.RECEIVED_ACTIVE_THREAD_CHANGED, - root_id: "", - parent_id: "" - }); - } }, componentDidUpdate: function() { this.resizePostHolder(); @@ -138,62 +114,6 @@ module.exports = React.createClass({ this.resizePostHolder(); this.setState({messageText: messageText}); - // look to see if the message begins with any carets to indicate that it's a reply - var replyMatch = messageText.match(/^\^+/g); - if (replyMatch) { - // the number of carets indicates how many message threads back we're replying to - var caretCount = replyMatch[0].length; - - // note that if someone else replies to this thread while a user is typing a reply, the message to which they're replying - // won't change unless they change the number of carets. this is probably the desired behaviour since we don't want the - // active message thread to change without the user noticing - if (caretCount != this.state.caretCount) { - this.setState({caretCount: caretCount}); - - var posts = PostStore.getCurrentPosts(); - - var rootId = ""; - - // find the nth most recent post that isn't a comment on another (ie it has no parent) where n is caretCount - for (var i = 0; i < posts.order.length; i++) { - var postId = posts.order[i]; - - if (posts.posts[postId].parent_id === "") { - caretCount -= 1; - - if (caretCount < 1) { - rootId = postId; - break; - } - } - } - - // only dispatch an event if something changed - if (rootId != this.state.rootId) { - // set the parent id to match the root id so that we're replying to the first post in the thread - var parentId = rootId; - - // alert the post list so that it can display the active thread - AppDispatcher.handleViewAction({ - type: ActionTypes.RECEIVED_ACTIVE_THREAD_CHANGED, - root_id: rootId, - parent_id: parentId - }); - } - } - } else { - if (this.state.caretCount > 0) { - this.setState({caretCount: 0}); - - // clear the active thread since there no longer is one - AppDispatcher.handleViewAction({ - type: ActionTypes.RECEIVED_ACTIVE_THREAD_CHANGED, - root_id: "", - parent_id: "" - }); - } - } - var draft = PostStore.getCurrentDraft(); if (!draft) { draft = {} @@ -256,12 +176,10 @@ module.exports = React.createClass({ }, componentDidMount: function() { ChannelStore.addChangeListener(this._onChange); - PostStore.addActiveThreadChangedListener(this._onActiveThreadChanged); this.resizePostHolder(); }, componentWillUnmount: function() { ChannelStore.removeChangeListener(this._onChange); - PostStore.removeActiveThreadChangedListener(this._onActiveThreadChanged); }, _onChange: function() { var channel_id = ChannelStore.getCurrentId(); @@ -278,11 +196,6 @@ module.exports = React.createClass({ this.setState({ channel_id: channel_id, messageText: messageText, initialText: messageText, submitting: false, limit_error: null, server_error: null, post_error: null, previews: previews, uploadsInProgress: uploadsInProgress }); } }, - _onActiveThreadChanged: function(rootId, parentId) { - // note that we register for our own events and set the state from there so we don't need to manually set - // our state and dispatch an event each time the active thread changes - this.setState({"rootId": rootId, "parentId": parentId}); - }, getInitialState: function() { PostStore.clearDraftUploads(); @@ -293,7 +206,7 @@ module.exports = React.createClass({ previews = draft['previews']; messageText = draft['message']; } - return { channel_id: ChannelStore.getCurrentId(), messageText: messageText, uploadsInProgress: 0, previews: previews, submitting: false, initialText: messageText, caretCount: 0 }; + return { channel_id: ChannelStore.getCurrentId(), messageText: messageText, uploadsInProgress: 0, previews: previews, submitting: false, initialText: messageText }; }, setUploads: function(val) { var oldInProgress = this.state.uploadsInProgress diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx index e3586ecde..e72a2d001 100644 --- a/web/react/components/post.jsx +++ b/web/react/components/post.jsx @@ -83,7 +83,7 @@ module.exports = React.createClass({
: null } -
+
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index 46f77660d..3f59d5843 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -22,8 +22,7 @@ function getStateFromStores() { return { post_list: PostStore.getCurrentPosts(), - channel: channel, - activeThreadRootId: "" + channel: channel }; } @@ -52,7 +51,6 @@ module.exports = React.createClass({ ChannelStore.addChangeListener(this._onChange); UserStore.addStatusesChangeListener(this._onTimeChange); SocketStore.addChangeListener(this._onSocketChange); - PostStore.addActiveThreadChangedListener(this._onActiveThreadChanged); $(".post-list-holder-by-time").perfectScrollbar(); @@ -133,7 +131,6 @@ module.exports = React.createClass({ ChannelStore.removeChangeListener(this._onChange); UserStore.removeStatusesChangeListener(this._onTimeChange); SocketStore.removeChangeListener(this._onSocketChange); - PostStore.removeActiveThreadChangedListener(this._onActiveThreadChanged); $('body').off('click.userpopover'); }, resize: function() { @@ -232,9 +229,6 @@ module.exports = React.createClass({ this.refs[id].forceUpdateInfo(); } }, - _onActiveThreadChanged: function(rootId, parentId) { - this.setState({"activeThreadRootId": rootId}); - }, getMorePosts: function(e) { e.preventDefault(); @@ -429,12 +423,9 @@ module.exports = React.createClass({ // it is the last comment if it is last post in the channel or the next post has a different root post var isLastComment = utils.isComment(post) && (i === 0 || posts[order[i-1]].root_id != post.root_id); - // check if this is part of the thread that we're currently replying to - var isActiveThread = this.state.activeThreadRootId && (post.id === this.state.activeThreadRootId || post.root_id === this.state.activeThreadRootId); - var postCtl = ( ); diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx index 0745fcdc3..ecf54ede6 100644 --- a/web/react/stores/post_store.jsx +++ b/web/react/stores/post_store.jsx @@ -18,7 +18,6 @@ var SEARCH_TERM_CHANGE_EVENT = 'search_term_change'; var SELECTED_POST_CHANGE_EVENT = 'selected_post_change'; var MENTION_DATA_CHANGE_EVENT = 'mention_data_change'; var ADD_MENTION_EVENT = 'add_mention'; -var ACTIVE_THREAD_CHANGED_EVENT = 'active_thread_changed'; var PostStore = assign({}, EventEmitter.prototype, { @@ -94,18 +93,6 @@ var PostStore = assign({}, EventEmitter.prototype, { this.removeListener(ADD_MENTION_EVENT, callback); }, - emitActiveThreadChanged: function(rootId, parentId) { - this.emit(ACTIVE_THREAD_CHANGED_EVENT, rootId, parentId); - }, - - addActiveThreadChangedListener: function(callback) { - this.on(ACTIVE_THREAD_CHANGED_EVENT, callback); - }, - - removeActiveThreadChangedListener: function(callback) { - this.removeListener(ACTIVE_THREAD_CHANGED_EVENT, callback); - }, - getCurrentPosts: function() { var currentId = ChannelStore.getCurrentId(); @@ -211,10 +198,6 @@ PostStore.dispatchToken = AppDispatcher.register(function(payload) { case ActionTypes.RECIEVED_ADD_MENTION: PostStore.emitAddMention(action.id, action.username); break; - case ActionTypes.RECEIVED_ACTIVE_THREAD_CHANGED: - PostStore.emitActiveThreadChanged(action.root_id, action.parent_id); - break; - default: } }); diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 77ce19530..b0882ff73 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -18,7 +18,6 @@ module.exports = { RECIEVED_POST_SELECTED: null, RECIEVED_MENTION_DATA: null, RECIEVED_ADD_MENTION: null, - RECEIVED_ACTIVE_THREAD_CHANGED: null, RECIEVED_PROFILES: null, RECIEVED_ME: null, diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss index df565d763..98b17120d 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -319,12 +319,6 @@ body.ios { max-width: 100%; @include legacy-pie-clearfix; } - &.active-thread__content { - // this still needs a final style applied to it - & .post-body { - font-weight: bold; - } - } } .post-image__columns { @include legacy-pie-clearfix; -- cgit v1.2.3-1-g7c22 From 2e6c41f7d7c2bd0bd19722762241b8da9ba5453a Mon Sep 17 00:00:00 2001 From: hmhealey Date: Thu, 30 Jul 2015 17:01:07 -0400 Subject: Changed mention highlighting code to split words on all non-word characters --- web/react/utils/utils.jsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'web') diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 8a4d92b85..9a663a936 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -416,7 +416,7 @@ module.exports.textToJsx = function(text, options) { var lines = text.split("\n"); for (var i = 0; i < lines.length; i++) { var line = lines[i]; - var words = line.split(" "); + var words = line.split(/(?=\W)/); var highlightSearchClass = ""; for (var z = 0; z < words.length; z++) { var word = words[z]; @@ -444,7 +444,7 @@ module.exports.textToJsx = function(text, options) { highlightSearchClass = " search-highlight"; } - inner.push({prefix}@{name}{suffix} ); + inner.push({prefix}@{name}{suffix}); } else if (testUrlMatch(word).length) { var match = testUrlMatch(word)[0]; var link = match.link; @@ -452,7 +452,7 @@ module.exports.textToJsx = function(text, options) { var prefix = word.substring(0,word.indexOf(match.text)); var suffix = word.substring(word.indexOf(match.text)+match.text.length); - inner.push({prefix}{match.text}{suffix} ); + inner.push({prefix}{match.text}{suffix}); } else if (trimWord.match(hashRegex)) { var suffix = word.match(puncEndRegex); @@ -463,7 +463,7 @@ module.exports.textToJsx = function(text, options) { highlightSearchClass = " search-highlight"; } - inner.push({prefix}{trimWord}{suffix} ); + inner.push({prefix}{trimWord}{suffix}); } else if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) { var suffix = word.match(puncEndRegex); @@ -473,15 +473,15 @@ module.exports.textToJsx = function(text, options) { if (searchTerm === trimWord.substring(1).toLowerCase()) { highlightSearchClass = " search-highlight"; } - inner.push({prefix}{trimWord}{suffix} ); + inner.push({prefix}{trimWord}{suffix}); } else { - inner.push({prefix}{module.exports.replaceHtmlEntities(trimWord)}{suffix} ); + inner.push({prefix}{module.exports.replaceHtmlEntities(trimWord)}{suffix}); } } else if (word === "") { // if word is empty dont include a span } else { - inner.push({module.exports.replaceHtmlEntities(word)} ); + inner.push({module.exports.replaceHtmlEntities(word)}); } highlightSearchClass = ""; } -- cgit v1.2.3-1-g7c22 From aaad5add260252f8663f4b29755d8ce31f477a7d Mon Sep 17 00:00:00 2001 From: Reed Garmsen Date: Wed, 29 Jul 2015 15:11:07 -0700 Subject: Added text to inform the user a session id is not available for a login attempt (no session created yet) --- web/react/components/access_history_modal.jsx | 52 ++++++++++++++------------- 1 file changed, 28 insertions(+), 24 deletions(-) (limited to 'web') diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx index 6cc8ec8a9..16768a119 100644 --- a/web/react/components/access_history_modal.jsx +++ b/web/react/components/access_history_modal.jsx @@ -15,13 +15,13 @@ function getStateFromStoresForAudits() { module.exports = React.createClass({ componentDidMount: function() { UserStore.addAuditsChangeListener(this._onChange); - $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function (e) { + $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function(e) { AsyncClient.getAudits(); }); var self = this; $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) { - self.setState({ moreInfo: [] }); + self.setState({moreInfo: []}); }); }, componentWillUnmount: function() { @@ -36,7 +36,7 @@ module.exports = React.createClass({ handleMoreInfo: function(index) { var newMoreInfo = this.state.moreInfo; newMoreInfo[index] = true; - this.setState({ moreInfo: newMoreInfo }); + this.setState({moreInfo: newMoreInfo}); }, getInitialState: function() { var initialState = getStateFromStoresForAudits(); @@ -57,24 +57,28 @@ module.exports = React.createClass({ newDate = (
{currentHistoryDate.toDateString()}
); } + if (!currentAudit.session_id && currentAudit.action.search('/users/login') !== -1) { + currentAudit.session_id = 'N/A (Login attempt)'; + } + accessList[i] = ( -
-
{newDate}
-
-
{newHistoryDate.toLocaleTimeString(navigator.language, {hour: '2-digit', minute:'2-digit'})}
-
-
{"IP: " + currentAudit.ip_address}
- { this.state.moreInfo[i] ? +
+
{newDate}
+
+
{newHistoryDate.toLocaleTimeString(navigator.language, {hour: '2-digit', minute: '2-digit'})}
+
+
{'IP: ' + currentAudit.ip_address}
+ {this.state.moreInfo[i] ?
-
{"Session ID: " + currentAudit.session_id}
-
{"URL: " + currentAudit.action.replace("/api/v1", "")}
+
{'Session ID: ' + currentAudit.session_id}
+
{'URL: ' + currentAudit.action.replace(/\/api\/v[1-9]/, '')}
: - More info + More info }
{i < this.state.audits.length - 1 ? -
+
: null } @@ -85,17 +89,17 @@ module.exports = React.createClass({ return (
-
- +
-- cgit v1.2.3-1-g7c22 From a541f09380ab2adbe4d0ba7c80ff72015767bd81 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Wed, 29 Jul 2015 10:09:11 -0400 Subject: image thumbnails now scale appropriately so there is no whitespace, also generalized some thumbnail loading code --- web/react/utils/constants.jsx | 2 ++ web/react/utils/utils.jsx | 44 ++++++++++++++++++++++++++++++++ web/sass-files/sass/partials/_files.scss | 7 ++++- 3 files changed, 52 insertions(+), 1 deletion(-) (limited to 'web') diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 77ce19530..3509c9514 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -52,6 +52,8 @@ module.exports = { MAX_DISPLAY_FILES: 5, MAX_UPLOAD_FILES: 5, MAX_FILE_SIZE: 50000000, // 50 MB + THUMBNAIL_WIDTH: 128, + THUMBNAIL_HEIGHT: 100, DEFAULT_CHANNEL: 'town-square', OFFTOPIC_CHANNEL: 'off-topic', POST_CHUNK_SIZE: 60, diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index e51f7f3f4..942264fdb 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -913,3 +913,47 @@ module.exports.getFileName = function(path) { var split = path.split('/'); return split[split.length - 1]; }; + +module.exports.loadThumbnails = function(filenames, self) { + if (filenames) { + var re1 = new RegExp(' ', 'g'); + var re2 = new RegExp('\\(', 'g'); + var re3 = new RegExp('\\)', 'g'); + for (var i = 0; i < filenames.length && i < Constants.MAX_DISPLAY_FILES; i++) { + var fileInfo = module.exports.splitFileLocation(filenames[i]); + if (Object.keys(fileInfo).length === 0) continue; + + var type = module.exports.getFileType(fileInfo.ext); + + // This is a temporary patch to fix issue with old files using absolute paths + if (fileInfo.path.indexOf("/api/v1/files/get") != -1) { + fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; + } + fileInfo.path = module.exports.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path; + + if (type === "image") { + $('').attr('src', fileInfo.path+'_thumb.jpg').load(function(path, name){ return function() { + $(this).remove(); + if (name in self.refs) { + var imgDiv = self.refs[name].getDOMNode(); + $(imgDiv).removeClass('post__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'); + } + + var url = path.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29'); + $(imgDiv).css('background-image', 'url('+url+'_thumb.jpg)'); + } + }}(fileInfo.path, filenames[i])); + } + } + } +} diff --git a/web/sass-files/sass/partials/_files.scss b/web/sass-files/sass/partials/_files.scss index ea7548267..ddc5e98bb 100644 --- a/web/sass-files/sass/partials/_files.scss +++ b/web/sass-files/sass/partials/_files.scss @@ -129,7 +129,12 @@ height: 100%; background-color: #FFF; background-repeat: no-repeat; - background-position: top left; + &.small { + background-position: center; + } + &.normal { + background-position: top left; + } } .post-image__thumbnail { width: 50%; -- cgit v1.2.3-1-g7c22 From 8542fbd1ace1296030dbfaceec774e41a6fe07a1 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Fri, 31 Jul 2015 09:39:53 -0400 Subject: removed uneccessary utils loadThumbnails function --- web/react/utils/utils.jsx | 44 -------------------------------------------- 1 file changed, 44 deletions(-) (limited to 'web') diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 942264fdb..e51f7f3f4 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -913,47 +913,3 @@ module.exports.getFileName = function(path) { var split = path.split('/'); return split[split.length - 1]; }; - -module.exports.loadThumbnails = function(filenames, self) { - if (filenames) { - var re1 = new RegExp(' ', 'g'); - var re2 = new RegExp('\\(', 'g'); - var re3 = new RegExp('\\)', 'g'); - for (var i = 0; i < filenames.length && i < Constants.MAX_DISPLAY_FILES; i++) { - var fileInfo = module.exports.splitFileLocation(filenames[i]); - if (Object.keys(fileInfo).length === 0) continue; - - var type = module.exports.getFileType(fileInfo.ext); - - // This is a temporary patch to fix issue with old files using absolute paths - if (fileInfo.path.indexOf("/api/v1/files/get") != -1) { - fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; - } - fileInfo.path = module.exports.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path; - - if (type === "image") { - $('').attr('src', fileInfo.path+'_thumb.jpg').load(function(path, name){ return function() { - $(this).remove(); - if (name in self.refs) { - var imgDiv = self.refs[name].getDOMNode(); - $(imgDiv).removeClass('post__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'); - } - - var url = path.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29'); - $(imgDiv).css('background-image', 'url('+url+'_thumb.jpg)'); - } - }}(fileInfo.path, filenames[i])); - } - } - } -} -- cgit v1.2.3-1-g7c22 From dad78514234029791ff02c9a0efd2cbacdac5280 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Fri, 31 Jul 2015 09:52:18 -0400 Subject: center small thumbnails and top left larger thumbnails --- web/react/components/file_attachment.jsx | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'web') diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx index 3cd791887..b7ea5734f 100644 --- a/web/react/components/file_attachment.jsx +++ b/web/react/components/file_attachment.jsx @@ -2,6 +2,7 @@ // See License.txt for license information. var utils = require('../utils/utils.jsx'); +var Constants = require('../utils/constants.jsx'); module.exports = React.createClass({ displayName: "FileAttachment", @@ -44,6 +45,16 @@ module.exports = React.createClass({ $(imgDiv).removeClass('post__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'); + } + var re1 = new RegExp(' ', 'g'); var re2 = new RegExp('\\(', 'g'); var re3 = new RegExp('\\)', 'g'); -- cgit v1.2.3-1-g7c22 From 56724980f80cd613789f77a0eae9da4779308965 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Fri, 31 Jul 2015 10:02:21 -0400 Subject: Revert "MM-1706 Changed mention highlighting code to split words on all non-word characters" --- web/react/utils/utils.jsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'web') diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index e51f7f3f4..09240bf06 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -416,7 +416,7 @@ module.exports.textToJsx = function(text, options) { var lines = text.split("\n"); for (var i = 0; i < lines.length; i++) { var line = lines[i]; - var words = line.split(/(?=\W)/); + var words = line.split(" "); var highlightSearchClass = ""; for (var z = 0; z < words.length; z++) { var word = words[z]; @@ -444,7 +444,7 @@ module.exports.textToJsx = function(text, options) { highlightSearchClass = " search-highlight"; } - inner.push({prefix}@{name}{suffix}); + inner.push({prefix}@{name}{suffix} ); } else if (testUrlMatch(word).length) { var match = testUrlMatch(word)[0]; var link = match.link; @@ -452,7 +452,7 @@ module.exports.textToJsx = function(text, options) { var prefix = word.substring(0,word.indexOf(match.text)); var suffix = word.substring(word.indexOf(match.text)+match.text.length); - inner.push({prefix}{match.text}{suffix}); + inner.push({prefix}{match.text}{suffix} ); } else if (trimWord.match(hashRegex)) { var suffix = word.match(puncEndRegex); @@ -463,7 +463,7 @@ module.exports.textToJsx = function(text, options) { highlightSearchClass = " search-highlight"; } - inner.push({prefix}{trimWord}{suffix}); + inner.push({prefix}{trimWord}{suffix} ); } else if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) { var suffix = word.match(puncEndRegex); @@ -473,15 +473,15 @@ module.exports.textToJsx = function(text, options) { if (searchTerm === trimWord.substring(1).toLowerCase()) { highlightSearchClass = " search-highlight"; } - inner.push({prefix}{trimWord}{suffix}); + inner.push({prefix}{trimWord}{suffix} ); } else { - inner.push({prefix}{module.exports.replaceHtmlEntities(trimWord)}{suffix}); + inner.push({prefix}{module.exports.replaceHtmlEntities(trimWord)}{suffix} ); } } else if (word === "") { // if word is empty dont include a span } else { - inner.push({module.exports.replaceHtmlEntities(word)}); + inner.push({module.exports.replaceHtmlEntities(word)} ); } highlightSearchClass = ""; } -- cgit v1.2.3-1-g7c22 From 4f235669e7df1b74655f623881e51d0b519da42a Mon Sep 17 00:00:00 2001 From: nickago Date: Fri, 31 Jul 2015 08:07:11 -0700 Subject: Updated actual text of error message --- web/react/components/signup_team_complete.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'web') diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx index fcc1f0e14..447a405bd 100644 --- a/web/react/components/signup_team_complete.jsx +++ b/web/react/components/signup_team_complete.jsx @@ -511,7 +511,7 @@ UsernamePage = React.createClass({ this.setState({name_error: 'This username is reserved, please choose a new one.'}); return; } else if (username_error) { - this.setState({name_error: "Username must begin with a letter, and contain between 3 to 15 characters made up of numbers, lowercase letters, and the symbols '.', '-', and '_'"}); + this.setState({name_error: "Username must begin with a letter, and contain 3 to 15 characters in total, which may be numbers, lowercase letters, or any of the symbols '.', '-', or '_'"}); return; } -- cgit v1.2.3-1-g7c22 From f7e5ac1db0952e1ae0cd99d0d1c47916d51258b0 Mon Sep 17 00:00:00 2001 From: nickago Date: Thu, 30 Jul 2015 14:51:31 -0700 Subject: Added singlequotes around 'Edit' and did cosmetic refactoring --- web/react/components/user_settings.jsx | 203 ++++++++++++++++++--------------- 1 file changed, 112 insertions(+), 91 deletions(-) (limited to 'web') diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx index 902989b7b..95d1178d1 100644 --- a/web/react/components/user_settings.jsx +++ b/web/react/components/user_settings.jsx @@ -638,8 +638,8 @@ var GeneralTab = React.createClass({ var username = this.state.username.trim(); var username_error = utils.isValidUsername(username); - if (username_error === "Cannot use a reserved word as a username.") { - this.setState({client_error: "This username is reserved, please choose a new one." }); + if (username_error === 'Cannot use a reserved word as a username.') { + this.setState({client_error: 'This username is reserved, please choose a new one.' }); return; } else if (username_error) { this.setState({client_error: "Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'." }); @@ -647,7 +647,7 @@ var GeneralTab = React.createClass({ } if (user.username === username) { - this.setState({client_error: "You must submit a new username"}); + this.setState({client_error: 'You must submit a new username'}); return; } @@ -662,7 +662,7 @@ var GeneralTab = React.createClass({ var nickname = this.state.nickname.trim(); if (user.nickname === nickname) { - this.setState({client_error: "You must submit a new nickname"}) + this.setState({client_error: 'You must submit a new nickname'}) return; } @@ -678,7 +678,7 @@ var GeneralTab = React.createClass({ var lastName = this.state.last_name.trim(); if (user.first_name === firstName && user.last_name === lastName) { - this.setState({client_error: "You must submit a new first or last name"}) + this.setState({client_error: 'You must submit a new first or last name'}) return; } @@ -698,7 +698,7 @@ var GeneralTab = React.createClass({ } if (email === '' || !utils.isEmail(email)) { - this.setState({ email_error: "Please enter a valid email address" }); + this.setState({ email_error: 'Please enter a valid email address' }); return; } @@ -708,16 +708,16 @@ var GeneralTab = React.createClass({ }, submitUser: function(user) { client.updateUser(user, - function(data) { - this.updateSection(""); + function() { + this.updateSection(''); AsyncClient.getMe(); }.bind(this), function(err) { state = this.getInitialState(); - if(err.message) { + if (err.message) { state.server_error = err.message; } else { - state.server_error = err + state.server_error = err; } this.setState(state); }.bind(this) @@ -726,22 +726,26 @@ var GeneralTab = React.createClass({ submitPicture: function(e) { e.preventDefault(); - if (!this.state.picture) return; + if (!this.state.picture) { + return; + } - if(!this.submitActive) return; + if (!this.submitActive) { + return; + } var picture = this.state.picture; - if(picture.type !== "image/jpeg" && picture.type !== "image/png") { - this.setState({client_error: "Only JPG or PNG images may be used for profile pictures"}); + if (picture.type !== 'image/jpeg' && picture.type !== 'image/png') { + this.setState({client_error: 'Only JPG or PNG images may be used for profile pictures'}); return; } - formData = new FormData(); + var formData = new FormData(); formData.append('image', picture, picture.name); client.uploadProfileImage(formData, - function(data) { + function() { this.submitActive = false; AsyncClient.getMe(); window.location.reload(); @@ -754,39 +758,39 @@ var GeneralTab = React.createClass({ ); }, updateUsername: function(e) { - this.setState({ username: e.target.value }); + this.setState({username: e.target.value}); }, updateFirstName: function(e) { - this.setState({ first_name: e.target.value }); + this.setState({first_name: e.target.value}); }, updateLastName: function(e) { - this.setState({ last_name: e.target.value}); + this.setState({last_name: e.target.value}); }, updateNickname: function(e) { this.setState({nickname: e.target.value}); }, updateEmail: function(e) { - this.setState({ email: e.target.value}); + this.setState({email: e.target.value}); }, updatePicture: function(e) { if (e.target.files && e.target.files[0]) { this.setState({ picture: e.target.files[0] }); this.submitActive = true; - this.setState({client_error:null}) + this.setState({client_error: null}); } else { - this.setState({ picture: null }); + this.setState({picture: null}); } }, updateSection: function(section) { - this.setState({client_error:""}) - this.submitActive = false + this.setState({client_error:''}); + this.submitActive = false; this.props.updateSection(section); }, handleClose: function() { - $(this.getDOMNode()).find(".form-control").each(function() { - this.value = ""; + $(this.getDOMNode()).find('.form-control').each(function() { + this.value = ''; }); this.setState(assign({}, this.getInitialState(), {client_error: null, server_error: null, email_error: null})); @@ -812,43 +816,45 @@ var GeneralTab = React.createClass({ var nameSection; var self = this; + var inputs = []; if (this.props.activeSection === 'name') { - var inputs = []; - inputs.push( -
- -
- +
+ +
+
); inputs.push( -
- -
- +
+ +
+
); nameSection = ( ); } else { - var full_name = ""; + var full_name = ''; if (user.first_name && user.last_name) { - full_name = user.first_name + " " + user.last_name; + full_name = user.first_name + ' ' + user.last_name; } else if (user.first_name) { full_name = user.first_name; } else if (user.last_name) { @@ -857,107 +863,119 @@ var GeneralTab = React.createClass({ nameSection = ( ); } var nicknameSection; if (this.props.activeSection === 'nickname') { - var inputs = []; inputs.push( -
- -
- +
+ +
+
); nicknameSection = ( ); } else { nicknameSection = ( ); } var usernameSection; if (this.props.activeSection === 'username') { - var inputs = []; - inputs.push( -
- -
- +
+ +
+
); usernameSection = ( ); } else { usernameSection = ( ); } var emailSection; if (this.props.activeSection === 'email') { - var inputs = []; - inputs.push( -
- -
- +
+ +
+
); emailSection = ( ); } else { emailSection = ( ); } @@ -966,57 +984,60 @@ var GeneralTab = React.createClass({ if (this.props.activeSection === 'picture') { pictureSection = ( ); - } else { - var minMessage = "Click Edit to upload an image."; + var minMessage = 'Click \'Edit\' to upload an image.'; if (user.last_picture_update) { - minMessage = "Image last updated " + utils.displayDate(user.last_picture_update) + minMessage = 'Image last updated ' + utils.displayDate(user.last_picture_update); } pictureSection = ( ); } return (
-
- -

General Settings

+
+ +

General Settings

-
-

General Settings

-
+
+

General Settings

+
{nameSection} -
+
{usernameSection} -
+
{nicknameSection} -
+
{emailSection} -
+
{pictureSection} -
+
); } }); - var AppearanceTab = React.createClass({ submitTheme: function(e) { e.preventDefault(); -- cgit v1.2.3-1-g7c22 From 633c39499b8a8ebbf334e41c002a787af0106ea2 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Fri, 31 Jul 2015 16:09:29 -0400 Subject: switching to a new PM channel now works in the more modal --- web/react/components/more_direct_channels.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'web') diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx index 182d8884d..901cd228f 100644 --- a/web/react/components/more_direct_channels.jsx +++ b/web/react/components/more_direct_channels.jsx @@ -2,6 +2,7 @@ // See License.txt for license information. var ChannelStore = require('../stores/channel_store.jsx'); +var TeamStore = require('../stores/team_store.jsx'); var utils = require('../utils/utils.jsx'); module.exports = React.createClass({ @@ -35,7 +36,7 @@ module.exports = React.createClass({ ); } else { return ( -
  • {badge}{channel.display_name}
  • +
  • {badge}{channel.display_name}
  • ); } }); -- cgit v1.2.3-1-g7c22