diff options
Diffstat (limited to 'web/react/components')
20 files changed, 218 insertions, 154 deletions
diff --git a/web/react/components/channel_invite_modal.jsx b/web/react/components/channel_invite_modal.jsx index d41453fab..1b8fe4199 100644 --- a/web/react/components/channel_invite_modal.jsx +++ b/web/react/components/channel_invite_modal.jsx @@ -10,111 +10,95 @@ var AsyncClient = require('../utils/async_client.jsx'); function getStateFromStores() { var users = UserStore.getActiveOnlyProfiles(); - var member_list = ChannelStore.getCurrentExtraInfo().members; + var memberIds = ChannelStore.getCurrentExtraInfo().members.map(function(user) { return user.id; }); - var nonmember_list = []; + var nonmembers = []; for (var id in users) { - var found = false; - for (var i = 0; i < member_list.length; i++) { - if (member_list[i].id === id) { - found = true; - break; - } - } - if (!found) { - nonmember_list.push(users[id]); + if (memberIds.indexOf(id) == -1) { + nonmembers.push(users[id]); } } - member_list.sort(function(a,b) { - if (a.username < b.username) return -1; - if (a.username > b.username) return 1; - return 0; - }); - - nonmember_list.sort(function(a,b) { - if (a.username < b.username) return -1; - if (a.username > b.username) return 1; - return 0; + nonmembers.sort(function(a,b) { + return a.username.localeCompare(b.username); }); var channel_name = ChannelStore.getCurrent() ? ChannelStore.getCurrent().display_name : ""; return { - nonmember_list: nonmember_list, - member_list: member_list, + nonmembers: nonmembers, + memberIds: memberIds, channel_name: channel_name }; } module.exports = React.createClass({ + displayName: "ChannelInviteModal", + + isShown: false, + getInitialState: function() { + return {}; + }, + componentDidMount: function() { + $(React.findDOMNode(this)) + .on('hidden.bs.modal', this._onHide) + .on('show.bs.modal', this._onShow); + }, + + _onShow: function() { ChannelStore.addExtraInfoChangeListener(this._onChange); ChannelStore.addChangeListener(this._onChange); - - var self = this; - $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) { - self.setState({ render_members: false }); - }); - - $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { - self.setState({ render_members: true }); - }); + this.isShown = true; + this._onChange(); }, - componentWillUnmount: function() { + + _onHide: function() { ChannelStore.removeExtraInfoChangeListener(this._onChange); ChannelStore.removeChangeListener(this._onChange); + this.isShown = false; }, + _onChange: function() { - var new_state = getStateFromStores(); - if (!utils.areStatesEqual(this.state, new_state)) { - this.setState(new_state); - } + this.setState(getStateFromStores()); }, + handleInvite: function(user_id) { // Make sure the user isn't already a member of the channel - var member_list = this.state.member_list; - for (var i = 0; i < member_list; i++) { - if (member_list[i].id === user_id) { - return; - } + if (this.state.memberIds.indexOf(user_id) > -1) { + return; } var data = {}; - data['user_id'] = user_id; + data.user_id = user_id; client.addChannelMember(ChannelStore.getCurrentId(), data, - function(data) { - var nonmember_list = this.state.nonmember_list; - var new_member; - for (var i = 0; i < nonmember_list.length; i++) { - if (user_id === nonmember_list[i].id) { - nonmember_list[i].invited = true; - new_member = nonmember_list[i]; + function() { + var nonmembers = this.state.nonmembers; + var memberIds = this.state.memberIds; + + for (var i = 0; i < nonmembers.length; i++) { + if (user_id === nonmembers[i].id) { + nonmembers[i].invited = true; + memberIds.push(user_id); break; } } - if (new_member) { - member_list.push(new_member); - member_list.sort(function(a,b) { - if (a.username < b.username) return -1; - if (a.username > b.username) return 1; - return 0; - }); - } - - this.setState({ invite_error: null, member_list: member_list, nonmember_list: nonmember_list }); + this.setState({ invite_error: null, memberIds: memberIds, nonmembers: nonmembers }); AsyncClient.getChannelExtraInfo(true); }.bind(this), + function(err) { this.setState({ invite_error: err.message }); }.bind(this) ); }, - getInitialState: function() { - return getStateFromStores(); + + shouldComponentUpdate: function(nextProps, nextState) { + return this.isShown && !utils.areStatesEqual(this.state, nextState); }, + render: function() { var invite_error = this.state.invite_error ? <label className='has-error control-label'>{this.state.invite_error}</label> : null; @@ -125,22 +109,19 @@ module.exports = React.createClass({ } return ( - <div className="modal fade" ref="modal" id="channel_invite" role="dialog" aria-hidden="true"> - <div className="modal-dialog"> + <div className="modal fade" id="channel_invite" tabIndex="-1" role="dialog" aria-hidden="true"> + <div className="modal-dialog" role="document"> <div className="modal-content"> <div className="modal-header"> - <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> + <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 className="modal-title">Add New Members to {this.state.channel_name}</h4> </div> <div className="modal-body"> { invite_error } - { this.state.render_members ? <MemberList - memberList={this.state.nonmember_list} - isAdmin={isAdmin} - handleInvite={this.handleInvite} - /> - : "" } + memberList={this.state.nonmembers} + isAdmin={isAdmin} + handleInvite={this.handleInvite} /> </div> <div className="modal-footer"> <button type="button" className="btn btn-default" data-dismiss="modal">Close</button> @@ -151,7 +132,3 @@ module.exports = React.createClass({ ); } }); - - - - diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx index 537a41d03..b7cb248db 100644 --- a/web/react/components/channel_loader.jsx +++ b/web/react/components/channel_loader.jsx @@ -5,12 +5,14 @@ to the server on page load. This is to prevent other React controls from spamming AsyncClient with requests. */ +var BrowserStore = require('../stores/browser_store.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var SocketStore = require('../stores/socket_store.jsx'); var Constants = require('../utils/constants.jsx'); module.exports = React.createClass({ componentDidMount: function() { + /* Start initial aysnc loads */ AsyncClient.getMe(); AsyncClient.getPosts(true); diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx index c88b548d1..fefac12d7 100644 --- a/web/react/components/delete_post_modal.jsx +++ b/web/react/components/delete_post_modal.jsx @@ -3,6 +3,7 @@ var Client = require('../utils/client.jsx'); var PostStore = require('../stores/post_store.jsx'); +var BrowserStore = require('../stores/browser_store.jsx'); var utils = require('../utils/utils.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); @@ -54,9 +55,9 @@ module.exports = React.createClass({ var self = this; $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { var newState = {}; - if(sessionStorage.getItem('edit_state_transfer')) { - newState = JSON.parse(sessionStorage.getItem('edit_state_transfer')); - sessionStorage.removeItem('edit_state_transfer'); + if(BrowserStore.getItem('edit_state_transfer')) { + newState = JSON.parse(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') }; diff --git a/web/react/components/edit_channel_modal.jsx b/web/react/components/edit_channel_modal.jsx index f1f4eca40..255654fd5 100644 --- a/web/react/components/edit_channel_modal.jsx +++ b/web/react/components/edit_channel_modal.jsx @@ -43,7 +43,7 @@ module.exports = React.createClass({ <h4 className="modal-title" ref="title">Edit {this.state.title} Description</h4> </div> <div className="modal-body"> - <textarea className="form-control" rows="6" ref="channelDesc" maxLength="1024" value={this.state.description} onChange={this.handleUserInput}></textarea> + <textarea className="form-control" style={{resize: "none"}} rows="6" ref="channelDesc" maxLength="1024" value={this.state.description} onChange={this.handleUserInput}></textarea> </div> <div className="modal-footer"> <button type="button" className="btn btn-default" data-dismiss="modal">Close</button> diff --git a/web/react/components/edit_post_modal.jsx b/web/react/components/edit_post_modal.jsx index 24c2d7322..d741e189b 100644 --- a/web/react/components/edit_post_modal.jsx +++ b/web/react/components/edit_post_modal.jsx @@ -13,7 +13,7 @@ module.exports = React.createClass({ if (updatedPost.message.length === 0) { var tempState = this.state; delete tempState.editText; - sessionStorage.setItem('edit_state_transfer', JSON.stringify(tempState)); + BrowserStore.setItem('edit_state_transfer', JSON.stringify(tempState)); $("#edit_post").modal('hide'); $("#delete_post").modal('show'); return; 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/get_link_modal.jsx b/web/react/components/get_link_modal.jsx index 69e565185..bbfdce63a 100644 --- a/web/react/components/get_link_modal.jsx +++ b/web/react/components/get_link_modal.jsx @@ -38,7 +38,7 @@ module.exports = React.createClass({ <div className="modal-body"> <p>{"The link below is used for open " + strings.TeamPlural + " or if you allowed your " + strings.Team + " members to sign up using their " + strings.Company + " email addresses."} </p> - <textarea className="form-control" readOnly="true" value={this.state.value}></textarea> + <textarea className="form-control" style={{resize: "none"}} readOnly="true" value={this.state.value}></textarea> </div> <div className="modal-footer"> <button type="button" className="btn btn-default" data-dismiss="modal">Close</button> diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx index d1672126d..94be2acd6 100644 --- a/web/react/components/invite_member_modal.jsx +++ b/web/react/components/invite_member_modal.jsx @@ -89,7 +89,12 @@ module.exports = React.createClass({ $(this.refs.modal.getDOMNode()).modal('hide'); }.bind(this), function(err) { - this.setState({ server_error: err }); + if (err.message === "This person is already on your team") { + email_errors[err.detailed_error] = err.message; + this.setState({ email_errors: email_errors }); + } + else + this.setState({ server_error: err.message}); }.bind(this) ); @@ -157,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> ); } @@ -198,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/login.jsx b/web/react/components/login.jsx index 3b6f96c2d..74c7d4065 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -7,6 +7,7 @@ var utils = require('../utils/utils.jsx'); var client = require('../utils/client.jsx'); var UserStore = require('../stores/user_store.jsx'); +var BrowserStore = require('../stores/browser_store.jsx'); var FindTeamDomain = React.createClass({ @@ -21,7 +22,7 @@ var FindTeamDomain = React.createClass({ return; } - if (!utils.isLocalStorageSupported()) { + if (!BrowserStore.isLocalStorageSupported()) { state.server_error = "This service requires local storage to be enabled. Please enable it or exit private browsing."; this.setState(state); return; @@ -114,7 +115,7 @@ module.exports = React.createClass({ return; } - if (!utils.isLocalStorageSupported()) { + if (!BrowserStore.isLocalStorageSupported()) { state.server_error = "This service requires local storage to be enabled. Please enable it or exit private browsing."; this.setState(state); return; diff --git a/web/react/components/member_list_item.jsx b/web/react/components/member_list_item.jsx index f0bbff8bd..357fd49a8 100644 --- a/web/react/components/member_list_item.jsx +++ b/web/react/components/member_list_item.jsx @@ -5,13 +5,17 @@ var ChannelStore = require('../stores/channel_store.jsx'); var UserStore = require('../stores/user_store.jsx'); module.exports = React.createClass({ - handleInvite: function() { + displayName: 'MemberListItem', + handleInvite: function(e) { + e.preventDefault(); this.props.handleInvite(this.props.member.id); }, - handleRemove: function() { + handleRemove: function(e) { + e.preventDefault(); this.props.handleRemove(this.props.member.id); }, - handleMakeAdmin: function() { + handleMakeAdmin: function(e) { + e.preventDefault(); this.props.handleMakeAdmin(this.props.member.id); }, render: function() { @@ -20,12 +24,6 @@ module.exports = React.createClass({ var isAdmin = this.props.isAdmin; var isMemberAdmin = member.roles.indexOf("admin") > -1; - if (member.roles === '') { - member.roles = 'Member'; - } else { - member.roles = member.roles.charAt(0).toUpperCase() + member.roles.slice(1); - } - var invite; if (member.invited && this.props.handleInvite) { invite = <span className="member-role">Added</span>; @@ -36,30 +34,28 @@ module.exports = React.createClass({ invite = ( <div className="dropdown member-drop"> <a href="#" className="dropdown-toggle theme" type="button" id="channel_header_dropdown" data-toggle="dropdown" aria-expanded="true"> - <span>{member.roles} </span> + <span className="text-capitalize">{member.roles || 'Member'} </span> <span className="caret"></span> </a> <ul className="dropdown-menu member-menu" role="menu" aria-labelledby="channel_header_dropdown"> { this.props.handleMakeAdmin ? - <li role="presentation"><a role="menuitem" onClick={self.handleMakeAdmin}>Make Admin</a></li> - : "" } + <li role="presentation"><a href="" role="menuitem" onClick={self.handleMakeAdmin}>Make Admin</a></li> + : null } { this.props.handleRemove ? - <li role="presentation"><a role="menuitem" onClick={self.handleRemove}>Remove Member</a></li> - : "" } + <li role="presentation"><a href="" role="menuitem" onClick={self.handleRemove}>Remove Member</a></li> + : null } </ul> </div> ); } else { - invite = <div className="member-drop"><span>{member.roles} </span><span className="caret invisible"></span></div>; + invite = <div className="member-role text-capitalize" style={{marginRight: 15}}>{member.roles || 'Member'}</div>; } - var email = member.email.length > 0 ? member.email : ""; - return ( <div className="row member-div"> <img className="post-profile-img pull-left" src={"/api/v1/users/" + member.id + "/image"} height="36" width="36" /> <span className="member-name">{member.username}</span> - <span className="member-email">{email}</span> + <span className="member-email">{member.email}</span> { invite } </div> ); diff --git a/web/react/components/member_list_team.jsx b/web/react/components/member_list_team.jsx index 3613d97d8..cfb473e5e 100644 --- a/web/react/components/member_list_team.jsx +++ b/web/react/components/member_list_team.jsx @@ -92,10 +92,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_list.jsx b/web/react/components/mention_list.jsx index b666fcfae..ba2c53612 100644 --- a/web/react/components/mention_list.jsx +++ b/web/react/components/mention_list.jsx @@ -23,8 +23,9 @@ module.exports = React.createClass({ } } ); - $(document).click(function() { - if($('#'+self.props.id).length && $('#'+self.props.id).get(0) !== $(':focus').get(0)) { + $(document).click(function(e) { + if (!($('#'+self.props.id).is(e.target) || $('#'+self.props.id).has(e.target).length || + ('mentionlist' in self.refs && $(self.refs['mentionlist'].getDOMNode()).has(e.target).length))) { self.setState({mentionText: "-1"}) } }); @@ -128,7 +129,7 @@ module.exports = React.createClass({ if (numMentions < 1) return (<div/>); - var height = (numMentions*37) + 2; + var height = (numMentions*36) + 4; var width = $('#'+this.props.id).parent().width(); var bottom = $(window).height() - $('#'+this.props.id).offset().top; var left = $('#'+this.props.id).offset().left; @@ -136,7 +137,7 @@ module.exports = React.createClass({ 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 ref="mentionlist" className="mentions-box" style={{height: height, width: width}}> { mentions } </div> </div> diff --git a/web/react/components/more_channels.jsx b/web/react/components/more_channels.jsx index be2a5e93c..c3ddc76f3 100644 --- a/web/react/components/more_channels.jsx +++ b/web/react/components/more_channels.jsx @@ -61,6 +61,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 +79,36 @@ 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="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>) + : <div ref="loadingscreen" className="loading-screen loading-screen--channel"> + <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> + } { server_error } </div> <div className="modal-footer"> diff --git a/web/react/components/new_channel.jsx b/web/react/components/new_channel.jsx index 13fa5b2cc..160241c1c 100644 --- a/web/react/components/new_channel.jsx +++ b/web/react/components/new_channel.jsx @@ -122,7 +122,7 @@ module.exports = React.createClass({ </div> <div className="form-group"> <label className='control-label'>Description</label> - <textarea className="form-control" ref="channel_desc" rows="3" placeholder="Description" maxLength="1024"></textarea> + <textarea className="form-control" style={{resize: "none"}} ref="channel_desc" rows="3" placeholder="Description" maxLength="1024"></textarea> </div> { server_error } </form> diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index fc5157ce6..d6dc9ce30 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -123,7 +123,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 +134,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(); diff --git a/web/react/components/post_right.jsx b/web/react/components/post_right.jsx index 2c28c5d9f..115ee87d4 100644 --- a/web/react/components/post_right.jsx +++ b/web/react/components/post_right.jsx @@ -282,7 +282,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 +340,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/sidebar.jsx b/web/react/components/sidebar.jsx index 0e4d38fe0..2095978e8 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -263,6 +263,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/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx index 587d8cb82..500ee231e 100644 --- a/web/react/components/signup_team_complete.jsx +++ b/web/react/components/signup_team_complete.jsx @@ -5,11 +5,12 @@ var utils = require('../utils/utils.jsx'); var client = require('../utils/client.jsx'); var UserStore = require('../stores/user_store.jsx'); -var constants = require('../utils/constants.jsx') +var BrowserStore = require('../stores/browser_store.jsx'); +var constants = require('../utils/constants.jsx'); WelcomePage = React.createClass({ submitNext: function (e) { - if (!utils.isLocalStorageSupported()) { + if (!BrowserStore.isLocalStorageSupported()) { this.setState({ storage_error: "This service requires local storage to be enabled. Please enable it or exit private browsing."} ); return; } @@ -32,7 +33,7 @@ WelcomePage = React.createClass({ this.setState(state); return; } - else if (!utils.isLocalStorageSupported()) { + else if (!BrowserStore.isLocalStorageSupported()) { state.email_error = "This service requires local storage to be enabled. Please enable it or exit private browsing."; this.setState(state); return; @@ -595,7 +596,7 @@ PasswordPage = React.createClass({ module.exports = React.createClass({ updateParent: function(state, skipSet) { - localStorage.setItem(this.props.hash, JSON.stringify(state)); + BrowserStore.setGlobalItem(this.props.hash, JSON.stringify(state)); if (!skipSet) { this.setState(state); @@ -604,7 +605,7 @@ module.exports = React.createClass({ getInitialState: function() { var props = null; try { - props = JSON.parse(localStorage.getItem(this.props.hash)); + props = JSON.parse(BrowserStore.getGlobalItem(this.props.hash)); } catch(parse_error) { } diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index b9f32f0bc..fb96cc99f 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -5,7 +5,7 @@ var utils = require('../utils/utils.jsx'); var client = require('../utils/client.jsx'); var UserStore = require('../stores/user_store.jsx'); - +var BrowserStore = require('../stores/browser_store.jsx'); module.exports = React.createClass({ handleSubmit: function(e) { @@ -53,7 +53,7 @@ module.exports = React.createClass({ UserStore.setLastEmail(this.state.user.email); UserStore.setCurrentUser(data); if (this.props.hash > 0) - localStorage.setItem(this.props.hash, JSON.stringify({wizard: "finished"})); + BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: "finished"})); window.location.href = '/channels/town-square'; }.bind(this), function(err) { @@ -75,7 +75,7 @@ module.exports = React.createClass({ getInitialState: function() { var props = null; try { - props = JSON.parse(localStorage.getItem(this.props.hash)); + props = JSON.parse(BrowserStore.getGlobalItem(this.props.hash)); } catch(parse_error) { } @@ -130,7 +130,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> ); } |