diff options
Diffstat (limited to 'web/react')
32 files changed, 427 insertions, 463 deletions
diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx index 1b389aa1d..b7cb248db 100644 --- a/web/react/components/channel_loader.jsx +++ b/web/react/components/channel_loader.jsx @@ -12,8 +12,6 @@ var Constants = require('../utils/constants.jsx'); module.exports = React.createClass({ componentDidMount: function() { - // Initalize stores - BrowserStore.initalize(); /* Start initial aysnc loads */ AsyncClient.getMe(); diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx index fefac12d7..11970bc2b 100644 --- a/web/react/components/delete_post_modal.jsx +++ b/web/react/components/delete_post_modal.jsx @@ -56,13 +56,13 @@ module.exports = React.createClass({ $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { var newState = {}; if(BrowserStore.getItem('edit_state_transfer')) { - newState = JSON.parse(BrowserStore.getItem('edit_state_transfer')); + newState = BrowserStore.getItem('edit_state_transfer'); BrowserStore.removeItem('edit_state_transfer'); } else { var button = e.relatedTarget; newState = { title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments') }; } - self.setState(newState) + self.setState(newState); }); PostStore.addSelectedPostChangeListener(this._onChange); }, diff --git a/web/react/components/edit_post_modal.jsx b/web/react/components/edit_post_modal.jsx index d741e189b..21b75bb6e 100644 --- a/web/react/components/edit_post_modal.jsx +++ b/web/react/components/edit_post_modal.jsx @@ -4,6 +4,7 @@ var Client = require('../utils/client.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var Textbox = require('./textbox.jsx'); +var BrowserStore = require('../stores/browser_store.jsx'); module.exports = React.createClass({ handleEdit: function(e) { @@ -13,14 +14,14 @@ module.exports = React.createClass({ if (updatedPost.message.length === 0) { var tempState = this.state; delete tempState.editText; - BrowserStore.setItem('edit_state_transfer', JSON.stringify(tempState)); + BrowserStore.setItem('edit_state_transfer', tempState); $("#edit_post").modal('hide'); $("#delete_post").modal('show'); return; } - updatedPost.id = this.state.post_id - updatedPost.channel_id = this.state.channel_id + updatedPost.id = this.state.post_id; + updatedPost.channel_id = this.state.channel_id; Client.updatePost(updatedPost, function(data) { diff --git a/web/react/components/error_bar.jsx b/web/react/components/error_bar.jsx index f23dc060e..d9d91ef51 100644 --- a/web/react/components/error_bar.jsx +++ b/web/react/components/error_bar.jsx @@ -9,7 +9,7 @@ var ActionTypes = Constants.ActionTypes; function getStateFromStores() { var error = ErrorStore.getLastError(); - if (error) { + if (error && error.message !== "There appears to be a problem with your internet connection") { return { message: error.message }; } else { return { message: null }; diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx index 9ff67ae1b..94be2acd6 100644 --- a/web/react/components/invite_member_modal.jsx +++ b/web/react/components/invite_member_modal.jsx @@ -162,25 +162,31 @@ module.exports = React.createClass({ invite_sections[index] = ( <div key={"key" + index}> <div> - <button type="button" className="btn remove__member" onClick={this.removeInviteFields.bind(this, index)}>×</button> + <button type="button" className="btn btn-link remove__member" onClick={this.removeInviteFields.bind(this, index)}><span className="fa fa-trash"></span></button> </div> <div className={ email_error ? "form-group invite has-error" : "form-group invite" }> <input onKeyUp={this.displayNameKeyUp} type="text" ref={"email"+index} className="form-control" placeholder="email@domain.com" maxLength="64" /> { email_error } </div> + <div className="row--invite"> { config.AllowInviteNames ? - <div className={ first_name_error ? "form-group invite has-error" : "form-group invite" }> - <input type="text" className="form-control" ref={"first_name"+index} placeholder="First name" maxLength="64" /> - { first_name_error } + <div className="col-sm-6"> + <div className={ first_name_error ? "form-group has-error" : "form-group" }> + <input type="text" className="form-control" ref={"first_name"+index} placeholder="First name" maxLength="64" /> + { first_name_error } + </div> </div> : "" } { config.AllowInviteNames ? - <div className={ last_name_error ? "form-group invite has-error" : "form-group invite" }> - <input type="text" className="form-control" ref={"last_name"+index} placeholder="Last name" maxLength="64" /> - { last_name_error } + <div className="col-sm-6"> + <div className={ last_name_error ? "form-group has-error" : "form-group" }> + <input type="text" className="form-control" ref={"last_name"+index} placeholder="Last name" maxLength="64" /> + { last_name_error } + </div> </div> : "" } </div> + </div> ); } @@ -203,7 +209,7 @@ module.exports = React.createClass({ <button type="button" className="btn btn-default" onClick={this.addInviteFields}>Add another</button> <br/> <br/> - <label className='control-label'>People invited automatically join Town Square channel.</label> + <span>People invited automatically join Town Square channel.</span> </div> <div className="modal-footer"> <button type="button" className="btn btn-default" data-dismiss="modal">Close</button> diff --git a/web/react/components/loading_screen.jsx b/web/react/components/loading_screen.jsx new file mode 100644 index 000000000..5905e519b --- /dev/null +++ b/web/react/components/loading_screen.jsx @@ -0,0 +1,24 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +module.exports = React.createClass({ + displayName: "LoadingScreen", + propTypes: { + position: React.PropTypes.oneOf(['absolute', 'fixed', 'relative', 'static', 'inherit']) + }, + getDefaultProps: function() { + return { position: 'relative' }; + }, + render: function() { + return ( + <div className="loading-screen" style={{position: this.props.position}}> + <div className="loading__content"> + <h3>Loading</h3> + <div className="round round-1"></div> + <div className="round round-2"></div> + <div className="round round-3"></div> + </div> + </div> + ); + } +}); diff --git a/web/react/components/member_list_item.jsx b/web/react/components/member_list_item.jsx index 357fd49a8..cf8c71d7e 100644 --- a/web/react/components/member_list_item.jsx +++ b/web/react/components/member_list_item.jsx @@ -23,6 +23,7 @@ module.exports = React.createClass({ var member = this.props.member; var isAdmin = this.props.isAdmin; var isMemberAdmin = member.roles.indexOf("admin") > -1; + var timestamp = UserStore.getCurrentUser().update_at; var invite; if (member.invited && this.props.handleInvite) { @@ -53,7 +54,7 @@ module.exports = React.createClass({ return ( <div className="row member-div"> - <img className="post-profile-img pull-left" src={"/api/v1/users/" + member.id + "/image"} height="36" width="36" /> + <img className="post-profile-img pull-left" src={"/api/v1/users/" + member.id + "/image?time=" + timestamp} height="36" width="36" /> <span className="member-name">{member.username}</span> <span className="member-email">{member.email}</span> { invite } diff --git a/web/react/components/member_list_team.jsx b/web/react/components/member_list_team.jsx index 3613d97d8..aa53c5db6 100644 --- a/web/react/components/member_list_team.jsx +++ b/web/react/components/member_list_team.jsx @@ -61,7 +61,8 @@ var MemberListTeamItem = React.createClass({ render: function() { var server_error = this.state.server_error ? <div style={{ clear: "both" }} className="has-error"><label className='has-error control-label'>{this.state.server_error}</label></div> : null; var user = this.props.user; - var currentRoles = "Member" + var currentRoles = "Member"; + var timestamp = UserStore.getCurrentUser().update_at; if (user.roles.length > 0) { currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1); @@ -83,7 +84,7 @@ var MemberListTeamItem = React.createClass({ return ( <div className="row member-div"> - <img className="post-profile-img pull-left" src={"/api/v1/users/" + user.id + "/image"} height="36" width="36" /> + <img className="post-profile-img pull-left" src={"/api/v1/users/" + user.id + "/image?time=" + timestamp} height="36" width="36" /> <span className="member-name">{user.full_name.trim() ? user.full_name : user.username}</span> <span className="member-email">{user.full_name.trim() ? user.username : email}</span> <div className="dropdown member-drop"> @@ -92,10 +93,10 @@ var MemberListTeamItem = React.createClass({ <span className="caret"></span> </a> <ul className="dropdown-menu member-menu" role="menu" aria-labelledby="channel_header_dropdown"> - { showMakeAdmin ? <li role="presentation"><a role="menuitem" onClick={this.handleMakeAdmin}>Make Admin</a></li> : "" } - { showMakeMember ? <li role="presentation"><a role="menuitem" onClick={this.handleMakeMember}>Make Member</a></li> : "" } - { showMakeActive ? <li role="presentation"><a role="menuitem" onClick={this.handleMakeActive}>Make Active</a></li> : "" } - { showMakeNotActive ? <li role="presentation"><a role="menuitem" onClick={this.handleMakeNotActive}>Make Inactive</a></li> : "" } + { showMakeAdmin ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleMakeAdmin}>Make Admin</a></li> : "" } + { showMakeMember ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleMakeMember}>Make Member</a></li> : "" } + { showMakeActive ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleMakeActive}>Make Active</a></li> : "" } + { showMakeNotActive ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleMakeNotActive}>Make Inactive</a></li> : "" } </ul> </div> { server_error } diff --git a/web/react/components/mention.jsx b/web/react/components/mention.jsx index 3c33ddf49..520b81cbb 100644 --- a/web/react/components/mention.jsx +++ b/web/react/components/mention.jsx @@ -1,5 +1,6 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. +var UserStore = require("../stores/user_store.jsx"); module.exports = React.createClass({ handleClick: function() { @@ -7,8 +8,9 @@ module.exports = React.createClass({ }, render: function() { var icon; + var timestamp = UserStore.getCurrentUser().update_at; if (this.props.id != null) { - icon = <span><img className="mention-img" src={"/api/v1/users/" + this.props.id + "/image"}/></span>; + icon = <span><img className="mention-img" src={"/api/v1/users/" + this.props.id + "/image?time=" + timestamp}/></span>; } else { icon = <span><i className="mention-img fa fa-users fa-2x"></i></span>; } diff --git a/web/react/components/mention_list.jsx b/web/react/components/mention_list.jsx index 3fac41073..103ff29bb 100644 --- a/web/react/components/mention_list.jsx +++ b/web/react/components/mention_list.jsx @@ -9,7 +9,12 @@ var Mention = require('./mention.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; +var MAX_HEIGHT_LIST = 292; +var MAX_ITEMS_IN_LIST = 25; +var ITEM_HEIGHT = 36; + module.exports = React.createClass({ + displayName: "MentionList", componentDidMount: function() { PostStore.addMentionDataChangeListener(this._onChange); @@ -72,7 +77,7 @@ module.exports = React.createClass({ }, render: function() { var mentionText = this.state.mentionText; - if (mentionText === '-1') return (<div/>); + if (mentionText === '-1') return null; var profiles = UserStore.getActiveOnlyProfiles(); var users = []; @@ -100,8 +105,7 @@ module.exports = React.createClass({ var mentions = {}; var index = 0; - for (var i = 0; i < users.length; i++) { - if (Object.keys(mentions).length >= 25) break; + for (var i = 0; i < users.length && index < MAX_ITEMS_IN_LIST; i++) { if (this.alreadyMentioned(users[i].username)) continue; var firstName = "", lastName = ""; @@ -127,17 +131,20 @@ module.exports = React.createClass({ } var numMentions = Object.keys(mentions).length; - if (numMentions < 1) return (<div/>); + if (numMentions < 1) return null; - var height = (numMentions*37) + 2; - var width = $('#'+this.props.id).parent().width(); - var bottom = $(window).height() - $('#'+this.props.id).offset().top; - var left = $('#'+this.props.id).offset().left; - var max_height = $('#'+this.props.id).offset().top - 10; + var $mention_tab = $('#'+this.props.id); + var maxHeight = Math.min(MAX_HEIGHT_LIST, $mention_tab.offset().top - 10); + var style = { + height: Math.min(maxHeight, (numMentions*ITEM_HEIGHT) + 4), + width: $mention_tab.parent().width(), + bottom: $(window).height() - $mention_tab.offset().top, + left: $mention_tab.offset().left + }; return ( - <div className="mentions--top" style={{height: height, width: width, bottom: bottom, left: left}}> - <div ref="mentionlist" className="mentions-box" style={{maxHeight: max_height, height: height, width: width}}> + <div className="mentions--top" style={style}> + <div ref="mentionlist" className="mentions-box"> { mentions } </div> </div> diff --git a/web/react/components/more_channels.jsx b/web/react/components/more_channels.jsx index be2a5e93c..007476f9b 100644 --- a/web/react/components/more_channels.jsx +++ b/web/react/components/more_channels.jsx @@ -7,6 +7,7 @@ var client = require('../utils/client.jsx'); var asyncClient = require('../utils/async_client.jsx'); var UserStore = require('../stores/user_store.jsx'); var ChannelStore = require('../stores/channel_store.jsx'); +var LoadingScreen = require('./loading_screen.jsx'); function getStateFromStores() { return { @@ -16,6 +17,8 @@ function getStateFromStores() { } module.exports = React.createClass({ + displayName: "MoreChannelsModal", + componentDidMount: function() { ChannelStore.addMoreChangeListener(this._onChange); $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function (e) { @@ -61,6 +64,10 @@ module.exports = React.createClass({ render: function() { var server_error = this.state.server_error ? <div className='form-group has-error'><label className='control-label'>{ this.state.server_error }</label></div> : null; var outter = this; + var moreChannels; + + if (this.state.channels != null) + moreChannels = this.state.channels; return ( <div className="modal fade" id="more_channels" ref="modal" tabIndex="-1" role="dialog" aria-hidden="true"> @@ -75,26 +82,28 @@ module.exports = React.createClass({ <button data-toggle="modal" data-target="#new_channel" data-channeltype={this.state.channel_type} type="button" className="btn btn-primary channel-create-btn" onClick={this.handleNewChannel}>Create New Channel</button> </div> <div className="modal-body"> - {this.state.channels.length ? - <table className="more-channel-table table"> - <tbody> - {this.state.channels.map(function(channel) { - return ( - <tr key={channel.id}> - <td> - <p className="more-channel-name">{channel.display_name}</p> - <p className="more-channel-description">{channel.description}</p> - </td> - <td className="td--action"><button onClick={outter.handleJoin.bind(outter, channel.id)} className="pull-right btn btn-primary">Join</button></td> - </tr> - ) - })} - </tbody> - </table> - : <div className="no-channel-message"> - <p className="primary-message">No more channels to join</p> - <p className="secondary-message">Click 'Create New Channel' to make a new one</p> - </div>} + {!moreChannels.loading ? + (moreChannels.length ? + <table className="more-channel-table table"> + <tbody> + {moreChannels.map(function(channel) { + return ( + <tr key={channel.id}> + <td> + <p className="more-channel-name">{channel.display_name}</p> + <p className="more-channel-description">{channel.description}</p> + </td> + <td className="td--action"><button onClick={outter.handleJoin.bind(outter, channel.id)} className="btn btn-primary">Join</button></td> + </tr> + ) + })} + </tbody> + </table> + : <div className="no-channel-message"> + <p className="primary-message">No more channels to join</p> + <p className="secondary-message">Click 'Create New Channel' to make a new one</p> + </div>) + : <LoadingScreen /> } { server_error } </div> <div className="modal-footer"> diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx index 04b5ba082..5457e1cd3 100644 --- a/web/react/components/post.jsx +++ b/web/react/components/post.jsx @@ -10,6 +10,7 @@ var UserStore = require('../stores/user_store.jsx'); var ActionTypes = Constants.ActionTypes; module.exports = React.createClass({ + displayName: "Post", componentDidMount: function() { $('.modal').on('show.bs.modal', function () { $('.modal-body').css('overflow-y', 'auto'); @@ -19,7 +20,7 @@ module.exports = React.createClass({ handleCommentClick: function(e) { e.preventDefault(); - data = {}; + var data = {}; data.order = [this.props.post.id]; data.posts = this.props.posts; @@ -48,7 +49,6 @@ module.exports = React.createClass({ var commentCount = 0; var commentRootId = parentPost ? post.root_id : post.id; - var rootUser = ""; for (var postId in posts) { if (posts[postId].root_id == commentRootId) { commentCount += 1; @@ -57,12 +57,7 @@ module.exports = React.createClass({ var error = this.state.error ? <div className='form-group has-error'><label className='control-label'>{ this.state.error }</label></div> : null; - if (this.props.sameRoot){ - rootUser = "same--root"; - } - else { - rootUser = "other--root"; - } + var rootUser = this.props.sameRoot ? "same--root" : "other--root"; var postType = ""; if (type != "Post"){ @@ -74,14 +69,16 @@ module.exports = React.createClass({ currentUserCss = "current--user"; } + var timestamp = UserStore.getCurrentUser().update_at; + return ( <div> <div id={post.id} className={"post " + this.props.sameUser + " " + rootUser + " " + postType + " " + currentUserCss}> { !this.props.hideProfilePic ? <div className="post-profile-img__container"> - <img className="post-profile-img" src={"/api/v1/users/" + post.user_id + "/image"} height="36" width="36" /> + <img className="post-profile-img" src={"/api/v1/users/" + post.user_id + "/image?time=" + timestamp} height="36" width="36" /> </div> - : "" } + : null } <div className="post__content"> <PostHeader post={post} sameRoot={this.props.sameRoot} commentCount={commentCount} handleCommentClick={this.handleCommentClick} isLastComment={this.props.isLastComment} /> <PostBody post={post} sameRoot={this.props.sameRoot} parentPost={parentPost} posts={posts} handleCommentClick={this.handleCommentClick} /> diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx index 7d5ef4d33..cf542a98f 100644 --- a/web/react/components/post_body.jsx +++ b/web/react/components/post_body.jsx @@ -120,7 +120,7 @@ module.exports = React.createClass({ return ( <div className="post-body"> { comment } - <p key={post.Id+"_message"} className={postClass}><span>{inner}</span></p> + <p key={post.id+"_message"} className={postClass}><span>{inner}</span></p> { filenames && filenames.length > 0 ? <div className="post-image__columns"> { postFiles } diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index fc5157ce6..9349d0240 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -8,6 +8,7 @@ var UserProfile = require( './user_profile.jsx' ); var AsyncClient = require('../utils/async_client.jsx'); var CreatePost = require('./create_post.jsx'); var Post = require('./post.jsx'); +var LoadingScreen = require('./loading_screen.jsx'); var SocketStore = require('../stores/socket_store.jsx'); var utils = require('../utils/utils.jsx'); var Client = require('../utils/client.jsx'); @@ -26,37 +27,8 @@ function getStateFromStores() { }; } -function changeColor(col, amt) { - - var usePound = false; - - if (col[0] == "#") { - col = col.slice(1); - usePound = true; - } - - var num = parseInt(col,16); - - var r = (num >> 16) + amt; - - if (r > 255) r = 255; - else if (r < 0) r = 0; - - var b = ((num >> 8) & 0x00FF) + amt; - - if (b > 255) b = 255; - else if (b < 0) b = 0; - - var g = (num & 0x0000FF) + amt; - - if (g > 255) g = 255; - else if (g < 0) g = 0; - - return (usePound?"#":"") + String("000000" + (g | (b << 8) | (r << 16)).toString(16)).slice(-6); - -} - module.exports = React.createClass({ + displayName: "PostList", scrollPosition: 0, preventScrollTrigger: false, gotMorePosts: false, @@ -69,7 +41,7 @@ module.exports = React.createClass({ utils.changeCss('a.theme', 'color:'+user.props.theme+'; fill:'+user.props.theme+'!important;'); utils.changeCss('div.theme', 'background-color:'+user.props.theme+';'); utils.changeCss('.btn.btn-primary', 'background: ' + user.props.theme+';'); - utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + changeColor(user.props.theme, -10) +';'); + utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, -10) +';'); utils.changeCss('.modal .modal-header', 'background: ' + user.props.theme+';'); utils.changeCss('.mention', 'background: ' + user.props.theme+';'); utils.changeCss('.mention-link', 'color: ' + user.props.theme+';'); @@ -123,7 +95,7 @@ module.exports = React.createClass({ $('.post-list__content div .post').removeClass('post--last'); $('.post-list__content div:last-child .post').addClass('post--last'); - $('body').on('mouseenter mouseleave', '.post:not(.post--comment.same--root)', function(ev){ + $('body').on('mouseenter mouseleave', '.post', function(ev){ if(ev.type === 'mouseenter'){ $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--after'); $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--before'); @@ -134,6 +106,17 @@ module.exports = React.createClass({ } }); + $('body').on('mouseenter mouseleave', '.post.post--comment.same--root', function(ev){ + if(ev.type === 'mouseenter'){ + $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--comment'); + $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--comment'); + } + else { + $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--comment'); + $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--comment'); + } + }); + }, componentDidUpdate: function() { this.resize(); @@ -150,24 +133,20 @@ module.exports = React.createClass({ $('body').off('click.userpopover'); }, resize: function() { + var post_holder = $(".post-list-holder-by-time")[0]; + this.preventScrollTrigger = true; if (this.gotMorePosts) { this.gotMorePosts = false; - var post_holder = $(".post-list-holder-by-time")[0]; - this.preventScrollTrigger = true; $(post_holder).scrollTop($(post_holder).scrollTop() + (post_holder.scrollHeight-this.oldScrollHeight) ); - $(post_holder).perfectScrollbar('update'); } else { - var post_holder = $(".post-list-holder-by-time")[0]; - this.preventScrollTrigger = true; if ($("#new_message")[0] && !this.scrolledToNew) { $(post_holder).scrollTop($(post_holder).scrollTop() + $("#new_message").offset().top - 63); - $(post_holder).perfectScrollbar('update'); this.scrolledToNew = true; } else { $(post_holder).scrollTop(post_holder.scrollHeight); - $(post_holder).perfectScrollbar('update'); } } + $(post_holder).perfectScrollbar('update'); }, _onChange: function() { var newState = getStateFromStores(); @@ -331,12 +310,15 @@ module.exports = React.createClass({ more_messages = ( <div className="channel-intro"> <div className="post-profile-img__container channel-intro-img"> - <img className="post-profile-img" src={"/api/v1/users/" + teammate.id + "/image"} height="50" width="50" /> + <img className="post-profile-img" src={"/api/v1/users/" + teammate.id + "/image?time=" + teammate.update_at} height="50" width="50" /> </div> <div className="channel-intro-profile"> <strong><UserProfile userId={teammate.id} /></strong> </div> - <p className="channel-intro-text">{"This is the start of your private message history with " + teammate_name + "." }<br/>{"Private messages and files shared here are not shown to people outside this area."}</p> + <p className="channel-intro-text"> + {"This is the start of your private message history with " + teammate_name + "." }<br/> + {"Private messages and files shared here are not shown to people outside this area."} + </p> </div> ); } else { @@ -399,7 +381,7 @@ module.exports = React.createClass({ { channel.type === 'P' ? " Only invited members can see this private group." : " Any member can join and read this channel." } <br/> <a className="intro-links" href="#" style={userStyle} data-toggle="modal" data-target="#edit_channel" data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}><i className="fa fa-pencil"></i>Set a description</a> - <a className="intro-links" style={userStyle} data-toggle="modal" data-target="#channel_invite"><i className="fa fa-user-plus"></i>Invite others to this {ui_type}</a> + <a className="intro-links" href="#" style={userStyle} data-toggle="modal" data-target="#channel_invite"><i className="fa fa-user-plus"></i>Invite others to this {ui_type}</a> </p> </div> ); @@ -409,37 +391,35 @@ module.exports = React.createClass({ var postCtls = []; - if (posts != undefined) { + if (posts) { var previousPostDay = posts[order[order.length-1]] ? utils.getDateForUnixTicks(posts[order[order.length-1]].create_at): new Date(); - var currentPostDay = new Date(); + var currentPostDay; for (var i = order.length-1; i >= 0; i--) { var post = posts[order[i]]; - var parentPost; + var parentPost = post.parent_id ? posts[post.parent_id] : null; - if (post.parent_id) { - parentPost = posts[post.parent_id]; - } else { - parentPost = null; - } + var sameUser = ''; + var sameRoot = false; + var hideProfilePic = false; + var prevPost = (i < order.length - 1) ? posts[order[i + 1]] : null; - var sameUser = i < order.length-1 && posts[order[i+1]].user_id === post.user_id && post.create_at - posts[order[i+1]].create_at <= 1000*60*5 ? "same--user" : ""; - var sameRoot = i < order.length-1 && post.root_id != "" && (posts[order[i+1]].id === post.root_id || posts[order[i+1]].root_id === post.root_id) ? true : false; + if (prevPost) { + sameUser = (prevPost.user_id === post.user_id) && (post.create_at - prevPost.create_at <= 1000*60*5) ? "same--user" : ""; + sameRoot = utils.isComment(post) && (prevPost.id === post.root_id || prevPost.root_id === post.root_id); - // we only hide the profile pic if the previous post is not a comment, the current post is not a comment, and the previous post was made by the same user as the current post - var hideProfilePic = i < order.length-1 && posts[order[i+1]].user_id === post.user_id && posts[order[i+1]].root_id === '' && post.root_id === ''; + // we only hide the profile pic if the previous post is not a comment, the current post is not a comment, and the previous post was made by the same user as the current post + hideProfilePic = (prevPost.user_id === post.user_id) && !utils.isComment(prevPost) && !utils.isComment(post); + } // check if it's the last comment in a consecutive string of comments on the same post - var isLastComment = false; - if (utils.isComment(post)) { - // it is the last comment if it is last post in the channel or the next post has a different root post - isLastComment = (i === 0 || posts[order[i-1]].root_id != post.root_id); - } + // 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); var postCtl = <Post sameUser={sameUser} sameRoot={sameRoot} post={post} parentPost={parentPost} key={post.id} posts={posts} hideProfilePic={hideProfilePic} isLastComment={isLastComment} />; currentPostDay = utils.getDateForUnixTicks(post.create_at); - if(currentPostDay.getDate() !== previousPostDay.getDate() || currentPostDay.getMonth() !== previousPostDay.getMonth() || currentPostDay.getFullYear() !== previousPostDay.getFullYear()) { + if (currentPostDay.toDateString() != previousPostDay.toDateString()) { postCtls.push( <div className="date-separator"> <hr className="separator__hr" /> @@ -458,20 +438,10 @@ module.exports = React.createClass({ ); } postCtls.push(postCtl); - previousPostDay = utils.getDateForUnixTicks(post.create_at); + previousPostDay = currentPostDay; } - } - else { - postCtls.push( - <div ref="loadingscreen" className="loading-screen"> - <div className="loading__content"> - <h3>Loading</h3> - <div id="round_1" className="round"></div> - <div id="round_2" className="round"></div> - <div id="round_3" className="round"></div> - </div> - </div> - ); + } else { + postCtls.push(<LoadingScreen position="absolute" />); } return ( @@ -486,5 +456,3 @@ module.exports = React.createClass({ ); } }); - - diff --git a/web/react/components/post_right.jsx b/web/react/components/post_right.jsx index 2c28c5d9f..408fbf83a 100644 --- a/web/react/components/post_right.jsx +++ b/web/react/components/post_right.jsx @@ -67,6 +67,7 @@ RootPost = React.createClass({ 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 type = "Post"; if (this.props.post.root_id.length > 0) { @@ -118,7 +119,7 @@ RootPost = React.createClass({ return ( <div className={"post post--root " + currentUserCss}> <div className="post-profile-img__container"> - <img className="post-profile-img" src={"/api/v1/users/" + this.props.post.user_id + "/image"} height="36" width="36" /> + <img className="post-profile-img" src={"/api/v1/users/" + this.props.post.user_id + "/image?time=" + timestamp} height="36" width="36" /> </div> <div className="post__content"> <ul className="post-header"> @@ -227,11 +228,12 @@ CommentPost = React.createClass({ } var message = utils.textToJsx(this.props.post.message); + var timestamp = UserStore.getCurrentUser().update_at; return ( <div className={commentClass + " " + currentUserCss}> <div className="post-profile-img__container"> - <img className="post-profile-img" src={"/api/v1/users/" + this.props.post.user_id + "/image"} height="36" width="36" /> + <img className="post-profile-img" src={"/api/v1/users/" + this.props.post.user_id + "/image?time=" + timestamp} height="36" width="36" /> </div> <div className="post__content"> <ul className="post-header"> @@ -282,7 +284,6 @@ module.exports = React.createClass({ componentDidMount: function() { PostStore.addSelectedPostChangeListener(this._onChange); PostStore.addChangeListener(this._onChangeAll); - $(".post-right__scroll").perfectScrollbar(); this.resize(); var self = this; $(window).resize(function(){ @@ -341,7 +342,7 @@ module.exports = React.createClass({ var height = $(window).height() - $('#error_bar').outerHeight() - 100; $(".post-right__scroll").css("height", height + "px"); $(".post-right__scroll").scrollTop(100000); - $(".post-right__scroll").perfectScrollbar('update'); + $(".post-right__scroll").perfectScrollbar(); }, render: function() { diff --git a/web/react/components/search_results.jsx b/web/react/components/search_results.jsx index 003a38b7e..156cf0120 100644 --- a/web/react/components/search_results.jsx +++ b/web/react/components/search_results.jsx @@ -76,6 +76,7 @@ SearchItem = React.createClass({ var message = utils.textToJsx(this.props.post.message, {searchTerm: this.props.term, noMentionHighlight: !this.props.isMentionSearch}); var channelName = ""; var channel = ChannelStore.get(this.props.post.channel_id) + var timestamp = UserStore.getCurrentUser().update_at; if (channel) { if (channel.type === 'D') { @@ -89,7 +90,7 @@ SearchItem = React.createClass({ <div className="search-item-container post" onClick={this.handleClick}> <div className="search-channel__name">{ channelName }</div> <div className="post-profile-img__container"> - <img className="post-profile-img" src={"/api/v1/users/" + this.props.post.user_id + "/image"} height="36" width="36" /> + <img className="post-profile-img" src={"/api/v1/users/" + this.props.post.user_id + "/image?time=" + timestamp} height="36" width="36" /> </div> <div className="post__content"> <ul className="post-header"> diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 0e4d38fe0..cae9425d3 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -249,11 +249,27 @@ var SidebarLoggedIn = React.createClass({ var repRegex = new RegExp("<br>", "g"); var post = JSON.parse(msg.props.post); - var msg = post.message.replace(repRegex, "\n").split("\n")[0].replace("<mention>", "").replace("</mention>", ""); + var msgProps = msg.props; + var msg = post.message.replace(repRegex, "\n").replace(/\n+/g, " ").replace("<mention>", "").replace("</mention>", ""); + if (msg.length > 50) { msg = msg.substring(0,49) + "..."; } - utils.notifyMe(title, username + " wrote: " + msg, channel); + + if (msg.length === 0) { + if (msgProps.image) { + utils.notifyMe(title, username + " uploaded an image", channel); + } + else if (msgProps.otherFile) { + utils.notifyMe(title, username + " uploaded a file", channel); + } + else { + utils.notifyMe(title, username + " did something new", channel); + } + } + else { + utils.notifyMe(title, username + " wrote: " + msg, channel); + } if (!user.notify_props || user.notify_props.desktop_sound === "true") { utils.ding(); } @@ -263,6 +279,10 @@ var SidebarLoggedIn = React.createClass({ if (ChannelStore.getCurrentId() != msg.channel_id) { AsyncClient.getChannels(true); } + } else if (msg.action == "user_added") { + if (UserStore.getCurrentId() === msg.user_id) { + AsyncClient.getChannels(true); + } } }, updateTitle: function() { diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx index 0b59d2036..54858a04d 100644 --- a/web/react/components/sidebar_header.jsx +++ b/web/react/components/sidebar_header.jsx @@ -77,7 +77,7 @@ var NavbarDropdown = React.createClass({ for (var i = 0; i < this.state.teams.length; i++) { var domain = this.state.teams[i]; - if (domain == utils.getSubDomain()) + if (domain == utils.getSubDomain()) continue; if (teams.length == 0) @@ -121,10 +121,15 @@ module.exports = React.createClass({ }, render: function() { var teamName = this.props.teamName ? this.props.teamName : config.SiteName; + var me = UserStore.getCurrentUser() return ( <div className="team__header theme"> - <a className="team__name" href="/channels/town-square">{ teamName }</a> + <img className="user__picture" src={"/api/v1/users/" + me.id + "/image?time=" + me.update_at} /> + <div className="header__info"> + <div className="user__name">@{me.username}</div> + <a className="team__name" href="/channels/town-square">{ teamName }</a> + </div> <NavbarDropdown teamType={this.props.teamType} /> </div> ); diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx index 500ee231e..9e2a13955 100644 --- a/web/react/components/signup_team_complete.jsx +++ b/web/react/components/signup_team_complete.jsx @@ -596,19 +596,14 @@ PasswordPage = React.createClass({ module.exports = React.createClass({ updateParent: function(state, skipSet) { - BrowserStore.setGlobalItem(this.props.hash, JSON.stringify(state)); + BrowserStore.setGlobalItem(this.props.hash, state); if (!skipSet) { this.setState(state); } }, getInitialState: function() { - var props = null; - try { - props = JSON.parse(BrowserStore.getGlobalItem(this.props.hash)); - } - catch(parse_error) { - } + var props = BrowserStore.getGlobalItem(this.props.hash); if (!props) { props = {}; @@ -628,7 +623,7 @@ module.exports = React.createClass({ props.data = this.props.data; } - return props ; + return props; }, render: function() { if (this.state.wizard == "welcome") { diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index 124e617bd..fa0c26017 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -17,7 +17,7 @@ module.exports = React.createClass({ return; } - var username_error = utils.isValidUsername(this.state.user.username) + var username_error = utils.isValidUsername(this.state.user.username); if (username_error === "Cannot use a reserved word as a username.") { this.setState({name_error: "This username is reserved, please choose a new one.", email_error: "", password_error: "", server_error: ""}); return; @@ -53,7 +53,7 @@ module.exports = React.createClass({ UserStore.setLastEmail(this.state.user.email); UserStore.setCurrentUser(data); if (this.props.hash > 0) - BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: "finished"})); + BrowserStore.setGlobalItem(this.props.hash, {wizard: "finished"}); window.location.href = '/channels/town-square'; }.bind(this), function(err) { @@ -73,12 +73,7 @@ module.exports = React.createClass({ ); }, getInitialState: function() { - var props = null; - try { - props = JSON.parse(BrowserStore.getGlobalItem(this.props.hash)); - } - catch(parse_error) { - } + var props = BrowserStore.getGlobalItem(this.props.hash); if (!props) { props = {}; @@ -91,7 +86,7 @@ module.exports = React.createClass({ props.original_email = this.props.email; } - return props ; + return props; }, render: function() { @@ -130,7 +125,7 @@ module.exports = React.createClass({ </div> { email } <label className="control-label">Password</label> - <div className={ name_error ? "form-group has-error" : "form-group" }> + <div className={ password_error ? "form-group has-error" : "form-group" }> <input type="password" ref="password" className="form-control" placeholder="" maxLength="128" /> { password_error } </div> diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx index 6b746aa78..ad50b7920 100644 --- a/web/react/components/textbox.jsx +++ b/web/react/components/textbox.jsx @@ -8,11 +8,23 @@ var SocketStore = require('../stores/socket_store.jsx'); var MsgTyping = require('./msg_typing.jsx'); var MentionList = require('./mention_list.jsx'); var CommandList = require('./command_list.jsx'); +var ErrorStore = require('../stores/error_store.jsx'); +var AsyncClient = require('../utils/async_client.jsx'); var utils = require('../utils/utils.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; +function getStateFromStores() { + var error = ErrorStore.getLastError(); + + if (error) { + return { message: error.message }; + } else { + return { message: null }; + } +} + module.exports = React.createClass({ caret: -1, addedMention: false, @@ -20,6 +32,7 @@ module.exports = React.createClass({ mentions: [], componentDidMount: function() { PostStore.addAddMentionListener(this._onChange); + ErrorStore.addChangeListener(this._onError); this.resize(); this.processMentions(); @@ -27,11 +40,44 @@ module.exports = React.createClass({ }, componentWillUnmount: function() { PostStore.removeAddMentionListener(this._onChange); + ErrorStore.removeChangeListener(this._onError); }, _onChange: function(id, username) { if (id !== this.props.id) return; this.addMention(username); }, + _onError: function() { + var errorState = getStateFromStores(); + + if (this.state.timerInterrupt != null) { + window.clearInterval(this.state.timerInterrupt); + this.setState({ timerInterrupt: null }); + } + + if (errorState.message === "There appears to be a problem with your internet connection") { + this.setState({ connection: "bad-connection" }); + var timerInterrupt = window.setInterval(this._onTimerInterrupt, 5000); + this.setState({ timerInterrupt: timerInterrupt }); + } + else { + this.setState({ connection: "" }); + } + }, + _onTimerInterrupt: function() { + //Since these should only happen when you have no connection and slightly briefly after any + //performance hit should not matter + if (this.state.connection === "bad-connection") { + AppDispatcher.handleServerAction({ + type: ActionTypes.RECIEVED_ERROR, + err: null + }); + + AsyncClient.updateLastViewedAt(); + } + + window.clearInterval(this.state.timerInterrupt); + this.setState({ timerInterrupt: null }); + }, componentDidUpdate: function() { if (this.caret >= 0) { utils.setCaretPosition(this.refs.message.getDOMNode(), this.caret) @@ -57,7 +103,7 @@ module.exports = React.createClass({ this.resize(); }, getInitialState: function() { - return { mentionText: '-1', mentions: [] }; + return { mentionText: '-1', mentions: [], connection: "", timerInterrupt: null }; }, updateMentionTab: function(mentionText, excludeList) { var self = this; @@ -287,7 +333,7 @@ module.exports = React.createClass({ <div ref="wrapper" className="textarea-wrapper"> <CommandList ref='commands' addCommand={this.addCommand} channelId={this.props.channelId} /> <div className="form-control textarea-div" ref="textdiv"/> - <textarea id={this.props.id} ref="message" className="form-control custom-textarea" spellCheck="true" autoComplete="off" autoCorrect="off" rows="1" placeholder={this.props.createMessage} value={this.props.messageText} onInput={this.handleChange} onChange={this.handleChange} onKeyPress={this.handleKeyPress} onKeyDown={this.handleKeyDown} onScroll={this.scroll} onFocus={this.handleFocus} onBlur={this.handleBlur} onPaste={this.handlePaste} /> + <textarea id={this.props.id} ref="message" className={"form-control custom-textarea " + this.state.connection} spellCheck="true" autoComplete="off" autoCorrect="off" rows="1" placeholder={this.props.createMessage} value={this.props.messageText} onInput={this.handleChange} onChange={this.handleChange} onKeyPress={this.handleKeyPress} onKeyDown={this.handleKeyDown} onScroll={this.scroll} onFocus={this.handleFocus} onBlur={this.handleBlur} onPaste={this.handlePaste} /> </div> ); } diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx index 648960471..89d0a80ff 100644 --- a/web/react/components/user_profile.jsx +++ b/web/react/components/user_profile.jsx @@ -53,7 +53,7 @@ module.exports = React.createClass({ var name = this.props.overwriteName ? this.props.overwriteName : this.state.profile.username; - var data_content = "<img style='margin: 10px' src='/api/v1/users/" + this.state.profile.id + "/image' height='128' width='128' />"; + var data_content = "<img style='margin: 10px' src='/api/v1/users/" + this.state.profile.id + "/image?time=" + this.state.profile.update_at + "' height='128' width='128' />"; if (!config.ShowEmail) { data_content += "<div class='text-nowrap'>Email not shared</div>"; } else { diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx index b4c3747af..06d8d0208 100644 --- a/web/react/components/user_settings.jsx +++ b/web/react/components/user_settings.jsx @@ -626,7 +626,7 @@ var SecurityTab = React.createClass({ client.updatePassword(data, function(data) { - this.updateSection(""); + this.props.updateSection(""); AsyncClient.getMe(); this.setState({ current_password: '', new_password: '', confirm_password: '' }); }.bind(this), diff --git a/web/react/stores/browser_store.jsx b/web/react/stores/browser_store.jsx index b3e54cad5..4d6eb0b8d 100644 --- a/web/react/stores/browser_store.jsx +++ b/web/react/stores/browser_store.jsx @@ -1,77 +1,104 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var UserStore = require('../stores/user_store.jsx'); +var UserStore; +function getPrefix() { + if (!UserStore) UserStore = require('./user_store.jsx'); + return UserStore.getCurrentId() + '_'; +} // Also change model/utils.go ETAG_ROOT_VERSION -var BROWSER_STORE_VERSION = '.1'; - -module.exports.initalize = function() { - var currentVersion = localStorage.getItem("local_storage_version"); - if (currentVersion !== BROWSER_STORE_VERSION) { - localStorage.clear(); - sessionStorage.clear(); - localStorage.setItem("local_storage_version", BROWSER_STORE_VERSION); - } -} +var BROWSER_STORE_VERSION = '.3'; -module.exports.setItem = function(name, value) { - var user_id = UserStore.getCurrentId(); - localStorage.setItem(user_id + "_" + name, value); -}; +module.exports = { + _initialized: false, -module.exports.getItem = function(name) { - var user_id = UserStore.getCurrentId(); - return localStorage.getItem(user_id + "_" + name); -}; + _initialize: function() { + var currentVersion = localStorage.getItem("local_storage_version"); + if (currentVersion !== BROWSER_STORE_VERSION) { + this.clear(); + localStorage.setItem("local_storage_version", BROWSER_STORE_VERSION); + } + this._initialized = true; + }, -module.exports.removeItem = function(name) { - var user_id = UserStore.getCurrentId(); - localStorage.removeItem(user_id + "_" + name); -}; + getItem: function(name, defaultValue) { + return this.getGlobalItem(getPrefix() + name, defaultValue); + }, -module.exports.setGlobalItem = function(name, value) { - localStorage.setItem(name, value); -}; + setItem: function(name, value) { + this.setGlobalItem(getPrefix() + name, value); + }, -module.exports.getGlobalItem = function(name) { - return localStorage.getItem(name); -}; + removeItem: function(name) { + if (!this._initialized) this._initialize(); -module.exports.removeGlobalItem = function(name) { - localStorage.removeItem(name); -}; + localStorage.removeItem(getPrefix() + name); + }, -module.exports.clear = function() { - localStorage.clear(); - sessionStorage.clear(); -}; + setGlobalItem: function(name, value) { + if (!this._initialized) this._initialize(); + + localStorage.setItem(name, JSON.stringify(value)); + }, + + getGlobalItem: function(name, defaultValue) { + if (!this._initialized) this._initialize(); + + var result = null; + try { + result = JSON.parse(localStorage.getItem(name)); + } catch (err) {} -// Preforms the given action on each item that has the given prefix -// Signiture for action is action(key, value) -module.exports.actionOnItemsWithPrefix = function (prefix, action) { - var user_id = UserStore.getCurrentId(); - var id_len = user_id.length; - var prefix_len = prefix.length; - for (key in localStorage) { - if (key.substring(id_len, id_len + prefix_len) === prefix) { - var userkey = key.substring(id_len); - action(userkey, BrowserStore.getItem(key)); + if (result === null && typeof defaultValue !== 'undefined') { + result = defaultValue; } - } -}; -module.exports.isLocalStorageSupported = function() { - try { - sessionStorage.setItem("testSession", '1'); - sessionStorage.removeItem("testSession"); + return result; + }, + + removeGlobalItem: function(name) { + if (!this._initialized) this._initialize(); + + localStorage.removeItem(name); + }, + + clear: function() { + localStorage.clear(); + sessionStorage.clear(); + }, + + /** + * Preforms the given action on each item that has the given prefix + * Signiture for action is action(key, value) + */ + actionOnItemsWithPrefix: function (prefix, action) { + if (!this._initialized) this._initialize(); - localStorage.setItem("testLocal", '1'); - localStorage.removeItem("testLocal", '1'); + var globalPrefix = getPrefix(); + var globalPrefixiLen = globalPrefix.length; + for (var key in localStorage) { + if (key.lastIndexOf(globalPrefix + prefix, 0) === 0) { + var userkey = key.substring(globalPrefixiLen); + action(userkey, this.getGlobalItem(key)); + } + } + }, + + isLocalStorageSupported: function() { + try { + sessionStorage.setItem("testSession", '1'); + sessionStorage.removeItem("testSession"); + + localStorage.setItem("testLocal", '1'); + if (localStorage.getItem("testLocal") != '1') { + return false; + } + localStorage.removeItem("testLocal", '1'); - return true; - } - catch (e) { - return false; + return true; + } catch (e) { + return false; + } } -} +}; diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx index 77ab69ef8..4a27e5f17 100644 --- a/web/react/stores/channel_store.jsx +++ b/web/react/stores/channel_store.jsx @@ -16,6 +16,7 @@ var MORE_CHANGE_EVENT = 'change'; var EXTRA_INFO_EVENT = 'extra_info'; var ChannelStore = assign({}, EventEmitter.prototype, { + _current_id: null, emitChange: function() { this.emit(CHANGE_EVENT); }, @@ -88,10 +89,7 @@ var ChannelStore = assign({}, EventEmitter.prototype, { return this._getMoreChannels(); }, setCurrentId: function(id) { - if (id == null) - BrowserStore.removeItem("current_channel_id"); - else - BrowserStore.setItem("current_channel_id", id); + this._current_id = id; }, setLastVisitedName: function(name) { if (name == null) @@ -117,10 +115,10 @@ var ChannelStore = assign({}, EventEmitter.prototype, { this._storeChannelMembers(cm); }, getCurrentId: function() { - return BrowserStore.getItem("current_channel_id"); + return this._current_id; }, getCurrent: function() { - var currentId = ChannelStore.getCurrentId(); + var currentId = this.getCurrentId(); if (currentId != null) return this.get(currentId); @@ -165,72 +163,35 @@ var ChannelStore = assign({}, EventEmitter.prototype, { return extra; }, _storeChannels: function(channels) { - BrowserStore.setItem("channels", JSON.stringify(channels)); + BrowserStore.setItem("channels", channels); }, _getChannels: function() { - var channels = []; - try { - channels = JSON.parse(BrowserStore.getItem("channels")); - } - catch (err) { - } - - if (channels == null) { - channels = []; - } - - return channels; + return BrowserStore.getItem("channels", []); }, _storeChannelMembers: function(channelMembers) { - BrowserStore.setItem("channel_members", JSON.stringify(channelMembers)); + BrowserStore.setItem("channel_members", channelMembers); }, _getChannelMembers: function() { - var members = {}; - try { - members = JSON.parse(BrowserStore.getItem("channel_members")); - } - catch (err) { - } - - if (members == null) { - members = {}; - } - - return members; + return BrowserStore.getItem("channel_members", {}); }, _storeMoreChannels: function(channels) { - BrowserStore.setItem("more_channels", JSON.stringify(channels)); + BrowserStore.setItem("more_channels", channels); }, _getMoreChannels: function() { - var channels = []; - try { - channels = JSON.parse(BrowserStore.getItem("more_channels")); - } - catch (err) { - } + var channels = BrowserStore.getItem("more_channels"); - if (channels == null) { - channels = []; - } + if (channels == null) { + channels = {}; + channels.loading = true; + } return channels; }, _storeExtraInfos: function(extraInfos) { - BrowserStore.setItem("extra_infos", JSON.stringify(extraInfos)); + BrowserStore.setItem("extra_infos", extraInfos); }, _getExtraInfos: function() { - var members = {}; - try { - members = JSON.parse(BrowserStore.getItem("extra_infos")); - } - catch (err) { - } - - if (members == null) { - members = {}; - } - - return members; + return BrowserStore.getItem("extra_infos", {}); } }); diff --git a/web/react/stores/error_store.jsx b/web/react/stores/error_store.jsx index c20e15680..203b692ec 100644 --- a/web/react/stores/error_store.jsx +++ b/web/react/stores/error_store.jsx @@ -29,18 +29,11 @@ var ErrorStore = assign({}, EventEmitter.prototype, { BrowserStore.removeItem("last_error"); }, getLastError: function() { - var error = null; - try { - error = JSON.parse(BrowserStore.last_error); - } - catch (err) { - } - - return error; + return BrowserStore.getItem('last_error'); }, _storeLastError: function(error) { - BrowserStore.setItem("last_error", JSON.stringify(error)); + BrowserStore.setItem("last_error", error); }, }); diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx index 8bf3fdcb2..e773bb688 100644 --- a/web/react/stores/post_store.jsx +++ b/web/react/stores/post_store.jsx @@ -106,55 +106,27 @@ var PostStore = assign({}, EventEmitter.prototype, { this.emitChange(); }, _storePosts: function(channelId, posts) { - BrowserStore.setItem("posts_" + channelId, JSON.stringify(posts)); + BrowserStore.setItem("posts_" + channelId, posts); }, getPosts: function(channelId) { - var posts = null; - try { - posts = JSON.parse(BrowserStore.getItem("posts_" + channelId)); - } - catch (err) { - } - - return posts; + return BrowserStore.getItem("posts_" + channelId); }, storeSearchResults: function(results, is_mention_search) { - BrowserStore.setItem("search_results", JSON.stringify(results)); + BrowserStore.setItem("search_results", results); is_mention_search = is_mention_search ? true : false; // force to bool - BrowserStore.setItem("is_mention_search", JSON.stringify(is_mention_search)); + BrowserStore.setItem("is_mention_search", is_mention_search); }, getSearchResults: function() { - var results = null; - try { - results = JSON.parse(BrowserStore.getItem("search_results")); - } - catch (err) { - } - - return results; + return BrowserStore.getItem("search_results"); }, getIsMentionSearch: function() { - var result = false; - try { - result = JSON.parse(BrowserStore.getItem("is_mention_search")); - } - catch (err) { - } - - return result; + return BrowserStore.getItem("is_mention_search"); }, storeSelectedPost: function(post_list) { - BrowserStore.setItem("select_post", JSON.stringify(post_list)); + BrowserStore.setItem("select_post", post_list); }, getSelectedPost: function() { - var post_list = null; - try { - post_list = JSON.parse(BrowserStore.getItem("select_post")); - } - catch (err) { - } - - return post_list; + return BrowserStore.getItem("select_post"); }, storeSearchTerm: function(term) { BrowserStore.setItem("search_term", term); @@ -165,25 +137,24 @@ var PostStore = assign({}, EventEmitter.prototype, { storeCurrentDraft: function(draft) { var channel_id = ChannelStore.getCurrentId(); var user_id = UserStore.getCurrentId(); - BrowserStore.setItem("draft_" + channel_id + "_" + user_id, JSON.stringify(draft)); + BrowserStore.setItem("draft_" + channel_id + "_" + user_id, draft); }, getCurrentDraft: function() { var channel_id = ChannelStore.getCurrentId(); var user_id = UserStore.getCurrentId(); - return JSON.parse(BrowserStore.getItem("draft_" + channel_id + "_" + user_id)); + return BrowserStore.getItem("draft_" + channel_id + "_" + user_id); }, storeDraft: function(channel_id, user_id, draft) { - BrowserStore.setItem("draft_" + channel_id + "_" + user_id, JSON.stringify(draft)); + BrowserStore.setItem("draft_" + channel_id + "_" + user_id, draft); }, getDraft: function(channel_id, user_id) { - return JSON.parse(BrowserStore.getItem("draft_" + channel_id + "_" + user_id)); + return BrowserStore.getItem("draft_" + channel_id + "_" + user_id); }, clearDraftUploads: function() { BrowserStore.actionOnItemsWithPrefix("draft_", function (key, value) { - var d = JSON.parse(value); - if (d) { - d['uploadsInProgress'] = 0; - BrowserStore.setItem(key, JSON.stringify(d)); + if (value) { + value.uploadsInProgress = 0; + BrowserStore.setItem(key, value); } }); } diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx index 39800ead5..8ebb854c9 100644 --- a/web/react/stores/socket_store.jsx +++ b/web/react/stores/socket_store.jsx @@ -10,8 +10,6 @@ var client = require('../utils/client.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; -var BrowserStore = require('../stores/browser_store.jsx'); - var CHANGE_EVENT = 'change'; var conn; diff --git a/web/react/stores/team_store.jsx b/web/react/stores/team_store.jsx index c494cb5b5..b7199a4a8 100644 --- a/web/react/stores/team_store.jsx +++ b/web/react/stores/team_store.jsx @@ -63,22 +63,10 @@ var TeamStore = assign({}, EventEmitter.prototype, { this._storeTeams(teams); }, _storeTeams: function(teams) { - BrowserStore.setItem("user_teams", JSON.stringify(teams)); + BrowserStore.setItem("user_teams", teams); }, _getTeams: function() { - var teams = {}; - - try { - teams = JSON.parse(BrowserStore.getItem("user_teams")); - } - catch (err) { - } - - if (teams == null) { - teams = {}; - } - - return teams; + return BrowserStore.getItem("user_teams", {}); } }); diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx index e832b34c7..93ddfec70 100644 --- a/web/react/stores/user_store.jsx +++ b/web/react/stores/user_store.jsx @@ -8,7 +8,7 @@ var client = require('../utils/client.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; -var BrowserStore = require('../stores/browser_store.jsx'); +var BrowserStore = require('./browser_store.jsx'); var CHANGE_EVENT = 'change'; var CHANGE_EVENT_SESSIONS = 'change_sessions'; @@ -18,6 +18,8 @@ var CHANGE_EVENT_STATUSES = 'change_statuses'; var UserStore = assign({}, EventEmitter.prototype, { + _current_id: null, + emitChange: function(userId) { this.emit(CHANGE_EVENT, userId); }, @@ -64,13 +66,10 @@ var UserStore = assign({}, EventEmitter.prototype, { this.removeListener(CHANGE_EVENT_STATUSES, callback); }, setCurrentId: function(id) { - if (id == null) - BrowserStore.removeGlobalItem("current_user_id"); - else - BrowserStore.setGlobalItem("current_user_id", id); + this._current_id = id; }, getCurrentId: function(skipFetch) { - var current_id = BrowserStore.getGlobalItem("current_user_id"); + var current_id = this._current_id; // this is a speical case to force fetch the // current user if it's missing @@ -97,21 +96,13 @@ var UserStore = assign({}, EventEmitter.prototype, { this.setCurrentId(user.id); }, getLastDomain: function() { - var last_domain = BrowserStore.getItem("last_domain"); - if (last_domain == null) { - last_domain = ""; - } - return last_domain; + return BrowserStore.getItem("last_domain", ''); }, setLastDomain: function(domain) { BrowserStore.setItem("last_domain", domain); }, getLastEmail: function() { - var last_email = BrowserStore.getItem("last_email"); - if (last_email == null) { - last_email = ""; - } - return last_email; + return BrowserStore.getItem("last_email", ''); }, setLastEmail: function(email) { BrowserStore.setItem("last_email", email); @@ -153,91 +144,36 @@ var UserStore = assign({}, EventEmitter.prototype, { this._storeProfiles(ps); }, _storeProfiles: function(profiles) { - BrowserStore.setGlobalItem("profiles", JSON.stringify(profiles)); + BrowserStore.setGlobalItem("profiles", profiles); var profileUsernameMap = {}; for (var id in profiles) { profileUsernameMap[profiles[id].username] = profiles[id]; } - BrowserStore.setGlobalItem("profileUsernameMap", JSON.stringify(profileUsernameMap)); + BrowserStore.setGlobalItem("profileUsernameMap", profileUsernameMap); }, _getProfiles: function() { - var profiles = {}; - try { - profiles = JSON.parse(BrowserStore.getGlobalItem("profiles")); - } - catch (err) { - } - - if (profiles == null) { - profiles = {}; - } - - return profiles; + return BrowserStore.getGlobalItem("profiles", {}); }, _getProfilesUsernameMap: function() { - var profileUsernameMap = {}; - try { - profileUsernameMap = JSON.parse(BrowserStore.getGlobalItem("profileUsernameMap")); - } - catch (err) { - } - - if (profileUsernameMap == null) { - profileUsernameMap = {}; - } - - return profileUsernameMap; + return BrowserStore.getGlobalItem("profileUsernameMap", {}); }, setSessions: function(sessions) { - BrowserStore.setItem("sessions", JSON.stringify(sessions)); + BrowserStore.setItem("sessions", sessions); }, getSessions: function() { - var sessions = []; - try { - sessions = JSON.parse(BrowserStore.getItem("sessions")); - } - catch (err) { - } - if (sessions == null) { - sessions = []; - } - - return sessions; + return BrowserStore.getItem("sessions", []); }, setAudits: function(audits) { - BrowserStore.setItem("audits", JSON.stringify(audits)); + BrowserStore.setItem("audits", audits); }, getAudits: function() { - var audits = []; - try { - audits = JSON.parse(BrowserStore.getItem("audits")); - } - catch (err) { - } - - if (audits == null) { - audits = []; - } - - return audits; + return BrowserStore.getItem("audits", []); }, setTeams: function(teams) { - BrowserStore.setItem("teams", JSON.stringify(teams)); + BrowserStore.setItem("teams", teams); }, getTeams: function() { - var teams = []; - try { - teams = JSON.parse(BrowserStore.getItem("teams")); - - } - catch (err) { - } - - if (teams == null) { - teams = []; - } - - return teams; + return BrowserStore.getItem("teams", []); }, getCurrentMentionKeys: function() { var user = this.getCurrentUser(); @@ -258,11 +194,7 @@ var UserStore = assign({}, EventEmitter.prototype, { } }, getLastVersion: function() { - var last_version = BrowserStore.getItem("last_version"); - if (last_version == null) { - last_version = ""; - } - return last_version; + return BrowserStore.getItem("last_version", ''); }, setLastVersion: function(version) { BrowserStore.setItem("last_version", version); @@ -272,7 +204,7 @@ var UserStore = assign({}, EventEmitter.prototype, { this.emitStatusesChange(); }, _setStatuses: function(statuses) { - BrowserStore.setItem("statuses", JSON.stringify(statuses)); + BrowserStore.setItem("statuses", statuses); }, setStatus: function(user_id, status) { var statuses = this.getStatuses(); @@ -281,18 +213,7 @@ var UserStore = assign({}, EventEmitter.prototype, { this.emitStatusesChange(); }, getStatuses: function() { - var statuses = {}; - try { - statuses = JSON.parse(BrowserStore.getItem("statuses")); - } - catch (err) { - } - - if (statuses == null) { - statuses = {}; - } - - return statuses; + return BrowserStore.getItem("statuses", {}); }, getStatus: function(id) { return this.getStatuses()[id]; @@ -341,4 +262,3 @@ UserStore.setMaxListeners(0); global.window.UserStore = UserStore; module.exports = UserStore; - diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx index 9383057c3..00bd83ed1 100644 --- a/web/react/utils/async_client.jsx +++ b/web/react/utils/async_client.jsx @@ -15,8 +15,6 @@ var ActionTypes = Constants.ActionTypes; var callTracker = {}; var dispatchError = function(err, method) { - if (err.message === "There appears to be a problem with your internet connection") return; - AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_ERROR, err: err, @@ -55,7 +53,7 @@ module.exports.getChannels = function(force, updateLastViewed, checkVersion) { if (checkVersion) { var serverVersion = xhr.getResponseHeader("X-Version-ID"); - if (UserStore.getLastVersion() == undefined) { + if (!UserStore.getLastVersion()) { UserStore.setLastVersion(serverVersion); } @@ -104,7 +102,7 @@ module.exports.updateLastViewedAt = function() { module.exports.getMoreChannels = function(force) { if (isCallInProgress("getMoreChannels")) return; - if (ChannelStore.getMoreAll().length == 0 || force) { + if (ChannelStore.getMoreAll().loading || force) { callTracker["getMoreChannels"] = utils.getTimestamp(); client.getMoreChannels( diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 5ded0e76f..19c074606 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -225,7 +225,7 @@ module.exports.extractLinks = function(text) { } return { "links": links, "text": text }; -} +} module.exports.escapeRegExp = function(string) { return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); @@ -671,13 +671,13 @@ module.exports.isValidUsername = function (name) { error = "First character must be a letter."; } - else + else { var lowerName = name.toLowerCase().trim(); - for (var i = 0; i < Constants.RESERVED_USERNAMES.length; i++) + for (var i = 0; i < Constants.RESERVED_USERNAMES.length; i++) { - if (lowerName === Constants.RESERVED_USERNAMES[i]) + if (lowerName === Constants.RESERVED_USERNAMES[i]) { error = "Cannot use a reserved word as a username."; break; @@ -782,3 +782,34 @@ module.exports.getHomeLink = function() { parts[0] = "www"; return window.location.protocol + "//" + parts.join("."); } + + +module.exports.changeColor =function(col, amt) { + + var usePound = false; + + if (col[0] == "#") { + col = col.slice(1); + usePound = true; + } + + var num = parseInt(col,16); + + var r = (num >> 16) + amt; + + if (r > 255) r = 255; + else if (r < 0) r = 0; + + var b = ((num >> 8) & 0x00FF) + amt; + + if (b > 255) b = 255; + else if (b < 0) b = 0; + + var g = (num & 0x0000FF) + amt; + + if (g > 255) g = 255; + else if (g < 0) g = 0; + + return (usePound?"#":"") + String("000000" + (g | (b << 8) | (r << 16)).toString(16)).slice(-6); + +}; |