summaryrefslogtreecommitdiffstats
path: root/web/react
diff options
context:
space:
mode:
Diffstat (limited to 'web/react')
-rw-r--r--web/react/components/channel_invite_modal.jsx127
-rw-r--r--web/react/components/channel_loader.jsx2
-rw-r--r--web/react/components/delete_post_modal.jsx7
-rw-r--r--web/react/components/edit_channel_modal.jsx2
-rw-r--r--web/react/components/edit_post_modal.jsx2
-rw-r--r--web/react/components/error_bar.jsx2
-rw-r--r--web/react/components/get_link_modal.jsx2
-rw-r--r--web/react/components/invite_member_modal.jsx29
-rw-r--r--web/react/components/login.jsx5
-rw-r--r--web/react/components/member_list_item.jsx32
-rw-r--r--web/react/components/member_list_team.jsx8
-rw-r--r--web/react/components/mention_list.jsx9
-rw-r--r--web/react/components/more_channels.jsx54
-rw-r--r--web/react/components/new_channel.jsx2
-rw-r--r--web/react/components/post_list.jsx13
-rw-r--r--web/react/components/post_right.jsx3
-rw-r--r--web/react/components/sidebar.jsx4
-rw-r--r--web/react/components/signup_team_complete.jsx11
-rw-r--r--web/react/components/signup_user_complete.jsx8
-rw-r--r--web/react/components/textbox.jsx50
-rw-r--r--web/react/package.json1
-rw-r--r--web/react/stores/browser_store.jsx85
-rw-r--r--web/react/stores/channel_store.jsx51
-rw-r--r--web/react/stores/error_store.jsx8
-rw-r--r--web/react/stores/post_store.jsx45
-rw-r--r--web/react/stores/socket_store.jsx2
-rw-r--r--web/react/stores/team_store.jsx16
-rw-r--r--web/react/stores/user_store.jsx107
-rw-r--r--web/react/utils/async_client.jsx6
-rw-r--r--web/react/utils/client.jsx3
-rw-r--r--web/react/utils/utils.jsx54
31 files changed, 468 insertions, 282 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">&times;</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>
);
}
diff --git a/web/react/package.json b/web/react/package.json
index 055530fea..8d9d57fab 100644
--- a/web/react/package.json
+++ b/web/react/package.json
@@ -8,7 +8,6 @@
"object-assign": "^1.0.0",
"react": "^0.12.0",
"autolinker": "^0.15.2",
- "linkify-it": "^1.1.0",
"react-zeroclipboard-mixin": "^0.1.0"
},
"devDependencies": {
diff --git a/web/react/stores/browser_store.jsx b/web/react/stores/browser_store.jsx
new file mode 100644
index 000000000..82cf9a942
--- /dev/null
+++ b/web/react/stores/browser_store.jsx
@@ -0,0 +1,85 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+var UserStore = require('../stores/user_store.jsx');
+
+// Also change model/utils.go ETAG_ROOT_VERSION
+var BROWSER_STORE_VERSION = '.1';
+
+var _initialized = false;
+
+function _initialize() {
+ var currentVersion = localStorage.getItem("local_storage_version");
+ if (currentVersion !== BROWSER_STORE_VERSION) {
+ localStorage.clear();
+ sessionStorage.clear();
+ localStorage.setItem("local_storage_version", BROWSER_STORE_VERSION);
+ }
+ _initialized = true;
+}
+
+module.exports.setItem = function(name, value) {
+ if (!_initialized) _initialize();
+ var user_id = UserStore.getCurrentId();
+ localStorage.setItem(user_id + "_" + name, value);
+};
+
+module.exports.getItem = function(name) {
+ if (!_initialized) _initialize();
+ var user_id = UserStore.getCurrentId();
+ return localStorage.getItem(user_id + "_" + name);
+};
+
+module.exports.removeItem = function(name) {
+ if (!_initialized) _initialize();
+ var user_id = UserStore.getCurrentId();
+ localStorage.removeItem(user_id + "_" + name);
+};
+
+module.exports.setGlobalItem = function(name, value) {
+ if (!_initialized) _initialize();
+ localStorage.setItem(name, value);
+};
+
+module.exports.getGlobalItem = function(name) {
+ if (!_initialized) _initialize();
+ return localStorage.getItem(name);
+};
+
+module.exports.removeGlobalItem = function(name) {
+ if (!_initialized) _initialize();
+ localStorage.removeItem(name);
+};
+
+module.exports.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)
+module.exports.actionOnItemsWithPrefix = function (prefix, action) {
+ var user_id = UserStore.getCurrentId();
+ var id_len = user_id.length;
+ var prefix_len = prefix.length;
+ for (var key in localStorage) {
+ if (key.substring(id_len, id_len + prefix_len) === prefix) {
+ var userkey = key.substring(id_len);
+ action(userkey, BrowserStore.getItem(key));
+ }
+ }
+};
+
+module.exports.isLocalStorageSupported = function() {
+ try {
+ sessionStorage.setItem("testSession", '1');
+ sessionStorage.removeItem("testSession");
+
+ localStorage.setItem("testLocal", '1');
+ localStorage.removeItem("testLocal", '1');
+
+ return true;
+ }
+ catch (e) {
+ return false;
+ }
+};
diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx
index 3f259bc7d..4429a5312 100644
--- a/web/react/stores/channel_store.jsx
+++ b/web/react/stores/channel_store.jsx
@@ -8,6 +8,8 @@ var assign = require('object-assign');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
+var BrowserStore = require('../stores/browser_store.jsx');
+
var CHANGE_EVENT = 'change';
var MORE_CHANGE_EVENT = 'change';
@@ -87,18 +89,18 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
},
setCurrentId: function(id) {
if (id == null)
- sessionStorage.removeItem("current_channel_id");
+ BrowserStore.removeItem("current_channel_id");
else
- sessionStorage.setItem("current_channel_id", id);
+ BrowserStore.setItem("current_channel_id", id);
},
setLastVisitedName: function(name) {
if (name == null)
- localStorage.removeItem("last_visited_name");
+ BrowserStore.removeItem("last_visited_name");
else
- localStorage.setItem("last_visited_name", name);
+ BrowserStore.setItem("last_visited_name", name);
},
getLastVisitedName: function() {
- return localStorage.getItem("last_visited_name");
+ return BrowserStore.getItem("last_visited_name");
},
resetCounts: function(id) {
var cm = this._getChannelMembers();
@@ -115,7 +117,7 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
this._storeChannelMembers(cm);
},
getCurrentId: function() {
- return sessionStorage.getItem("current_channel_id");
+ return BrowserStore.getItem("current_channel_id");
},
getCurrent: function() {
var currentId = ChannelStore.getCurrentId();
@@ -163,54 +165,71 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
return extra;
},
_storeChannels: function(channels) {
- sessionStorage.setItem("channels", JSON.stringify(channels));
+ BrowserStore.setItem("channels", JSON.stringify(channels));
},
_getChannels: function() {
var channels = [];
try {
- channels = JSON.parse(sessionStorage.channels);
+ channels = JSON.parse(BrowserStore.getItem("channels"));
}
catch (err) {
}
+ if (channels == null) {
+ channels = [];
+ }
+
return channels;
},
_storeChannelMembers: function(channelMembers) {
- sessionStorage.setItem("channel_members", JSON.stringify(channelMembers));
+ BrowserStore.setItem("channel_members", JSON.stringify(channelMembers));
},
_getChannelMembers: function() {
var members = {};
try {
- members = JSON.parse(sessionStorage.channel_members);
+ members = JSON.parse(BrowserStore.getItem("channel_members"));
}
catch (err) {
}
+ if (members == null) {
+ members = {};
+ }
+
return members;
},
_storeMoreChannels: function(channels) {
- sessionStorage.setItem("more_channels", JSON.stringify(channels));
+ BrowserStore.setItem("more_channels", JSON.stringify(channels));
},
_getMoreChannels: function() {
- var channels = [];
+ var channels = null;
try {
- channels = JSON.parse(sessionStorage.more_channels);
+ channels = JSON.parse(BrowserStore.getItem("more_channels"));
}
catch (err) {
+ }
+
+ if (channels == null) {
+ channels = {};
+ channels.loading = true;
}
return channels;
},
_storeExtraInfos: function(extraInfos) {
- sessionStorage.setItem("extra_infos", JSON.stringify(extraInfos));
+ BrowserStore.setItem("extra_infos", JSON.stringify(extraInfos));
},
_getExtraInfos: function() {
var members = {};
try {
- members = JSON.parse(sessionStorage.extra_infos);
+ members = JSON.parse(BrowserStore.getItem("extra_infos"));
}
catch (err) {
- }
+ }
+
+ if (members == null) {
+ members = {};
+ }
return members;
}
diff --git a/web/react/stores/error_store.jsx b/web/react/stores/error_store.jsx
index 82170034a..3aed6aef2 100644
--- a/web/react/stores/error_store.jsx
+++ b/web/react/stores/error_store.jsx
@@ -8,6 +8,8 @@ var assign = require('object-assign');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
+var BrowserStore = require('../stores/browser_store.jsx');
+
var CHANGE_EVENT = 'change';
var ErrorStore = assign({}, EventEmitter.prototype, {
@@ -24,12 +26,12 @@ var ErrorStore = assign({}, EventEmitter.prototype, {
this.removeListener(CHANGE_EVENT, callback);
},
handledError: function() {
- sessionStorage.removeItem("last_error");
+ BrowserStore.removeItem("last_error");
},
getLastError: function() {
var error = null;
try {
- error = JSON.parse(sessionStorage.last_error);
+ error = JSON.parse(BrowserStore.getItem("last_error"));
}
catch (err) {
}
@@ -38,7 +40,7 @@ var ErrorStore = assign({}, EventEmitter.prototype, {
},
_storeLastError: function(error) {
- sessionStorage.setItem("last_error", JSON.stringify(error));
+ BrowserStore.setItem("last_error", JSON.stringify(error));
},
});
diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx
index 05479f444..8bf3fdcb2 100644
--- a/web/react/stores/post_store.jsx
+++ b/web/react/stores/post_store.jsx
@@ -7,6 +7,7 @@ var assign = require('object-assign');
var ChannelStore = require('../stores/channel_store.jsx');
var UserStore = require('../stores/user_store.jsx');
+var BrowserStore = require('../stores/browser_store.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
@@ -105,12 +106,12 @@ var PostStore = assign({}, EventEmitter.prototype, {
this.emitChange();
},
_storePosts: function(channelId, posts) {
- sessionStorage.setItem("posts_" + channelId, JSON.stringify(posts));
+ BrowserStore.setItem("posts_" + channelId, JSON.stringify(posts));
},
getPosts: function(channelId) {
var posts = null;
try {
- posts = JSON.parse(sessionStorage.getItem("posts_" + channelId));
+ posts = JSON.parse(BrowserStore.getItem("posts_" + channelId));
}
catch (err) {
}
@@ -118,14 +119,14 @@ var PostStore = assign({}, EventEmitter.prototype, {
return posts;
},
storeSearchResults: function(results, is_mention_search) {
- sessionStorage.setItem("search_results", JSON.stringify(results));
+ BrowserStore.setItem("search_results", JSON.stringify(results));
is_mention_search = is_mention_search ? true : false; // force to bool
- sessionStorage.setItem("is_mention_search", JSON.stringify(is_mention_search));
+ BrowserStore.setItem("is_mention_search", JSON.stringify(is_mention_search));
},
getSearchResults: function() {
var results = null;
try {
- results = JSON.parse(sessionStorage.getItem("search_results"));
+ results = JSON.parse(BrowserStore.getItem("search_results"));
}
catch (err) {
}
@@ -135,7 +136,7 @@ var PostStore = assign({}, EventEmitter.prototype, {
getIsMentionSearch: function() {
var result = false;
try {
- result = JSON.parse(sessionStorage.getItem("is_mention_search"));
+ result = JSON.parse(BrowserStore.getItem("is_mention_search"));
}
catch (err) {
}
@@ -143,12 +144,12 @@ var PostStore = assign({}, EventEmitter.prototype, {
return result;
},
storeSelectedPost: function(post_list) {
- sessionStorage.setItem("select_post", JSON.stringify(post_list));
+ BrowserStore.setItem("select_post", JSON.stringify(post_list));
},
getSelectedPost: function() {
var post_list = null;
try {
- post_list = JSON.parse(sessionStorage.getItem("select_post"));
+ post_list = JSON.parse(BrowserStore.getItem("select_post"));
}
catch (err) {
}
@@ -156,37 +157,35 @@ var PostStore = assign({}, EventEmitter.prototype, {
return post_list;
},
storeSearchTerm: function(term) {
- sessionStorage.setItem("search_term", term);
+ BrowserStore.setItem("search_term", term);
},
getSearchTerm: function() {
- return sessionStorage.getItem("search_term");
+ return BrowserStore.getItem("search_term");
},
storeCurrentDraft: function(draft) {
var channel_id = ChannelStore.getCurrentId();
var user_id = UserStore.getCurrentId();
- localStorage.setItem("draft_" + channel_id + "_" + user_id, JSON.stringify(draft));
+ BrowserStore.setItem("draft_" + channel_id + "_" + user_id, JSON.stringify(draft));
},
getCurrentDraft: function() {
var channel_id = ChannelStore.getCurrentId();
var user_id = UserStore.getCurrentId();
- return JSON.parse(localStorage.getItem("draft_" + channel_id + "_" + user_id));
+ return JSON.parse(BrowserStore.getItem("draft_" + channel_id + "_" + user_id));
},
storeDraft: function(channel_id, user_id, draft) {
- localStorage.setItem("draft_" + channel_id + "_" + user_id, JSON.stringify(draft));
+ BrowserStore.setItem("draft_" + channel_id + "_" + user_id, JSON.stringify(draft));
},
getDraft: function(channel_id, user_id) {
- return JSON.parse(localStorage.getItem("draft_" + channel_id + "_" + user_id));
+ return JSON.parse(BrowserStore.getItem("draft_" + channel_id + "_" + user_id));
},
clearDraftUploads: function() {
- for (key in localStorage) {
- if (key.substring(0,6) === "draft_") {
- var d = JSON.parse(localStorage.getItem(key));
- if (d) {
- d['uploadsInProgress'] = 0;
- localStorage.setItem(key, JSON.stringify(d));
- }
- }
- }
+ BrowserStore.actionOnItemsWithPrefix("draft_", function (key, value) {
+ var d = JSON.parse(value);
+ if (d) {
+ d['uploadsInProgress'] = 0;
+ BrowserStore.setItem(key, JSON.stringify(d));
+ }
+ });
}
});
diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx
index 8ebb854c9..39800ead5 100644
--- a/web/react/stores/socket_store.jsx
+++ b/web/react/stores/socket_store.jsx
@@ -10,6 +10,8 @@ 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 e95daeeba..c494cb5b5 100644
--- a/web/react/stores/team_store.jsx
+++ b/web/react/stores/team_store.jsx
@@ -7,7 +7,7 @@ var assign = require('object-assign');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
-
+var BrowserStore = require('../stores/browser_store.jsx');
var CHANGE_EVENT = 'change';
@@ -42,12 +42,12 @@ var TeamStore = assign({}, EventEmitter.prototype, {
},
setCurrentId: function(id) {
if (id == null)
- sessionStorage.removeItem("current_team_id");
+ BrowserStore.removeItem("current_team_id");
else
- sessionStorage.setItem("current_team_id", id);
+ BrowserStore.setItem("current_team_id", id);
},
getCurrentId: function() {
- return sessionStorage.getItem("current_team_id");
+ return BrowserStore.getItem("current_team_id");
},
getCurrent: function() {
var currentId = TeamStore.getCurrentId();
@@ -63,17 +63,21 @@ var TeamStore = assign({}, EventEmitter.prototype, {
this._storeTeams(teams);
},
_storeTeams: function(teams) {
- sessionStorage.setItem("user_teams", JSON.stringify(teams));
+ BrowserStore.setItem("user_teams", JSON.stringify(teams));
},
_getTeams: function() {
var teams = {};
try {
- teams = JSON.parse(sessionStorage.user_teams);
+ teams = JSON.parse(BrowserStore.getItem("user_teams"));
}
catch (err) {
}
+ if (teams == null) {
+ teams = {};
+ }
+
return teams;
}
});
diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx
index e1df4879f..e832b34c7 100644
--- a/web/react/stores/user_store.jsx
+++ b/web/react/stores/user_store.jsx
@@ -8,6 +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 CHANGE_EVENT = 'change';
var CHANGE_EVENT_SESSIONS = 'change_sessions';
@@ -64,12 +65,12 @@ var UserStore = assign({}, EventEmitter.prototype, {
},
setCurrentId: function(id) {
if (id == null)
- sessionStorage.removeItem("current_user_id");
+ BrowserStore.removeGlobalItem("current_user_id");
else
- sessionStorage.setItem("current_user_id", id);
+ BrowserStore.setGlobalItem("current_user_id", id);
},
getCurrentId: function(skipFetch) {
- var current_id = sessionStorage.current_user_id;
+ var current_id = BrowserStore.getGlobalItem("current_user_id");
// this is a speical case to force fetch the
// current user if it's missing
@@ -96,16 +97,24 @@ var UserStore = assign({}, EventEmitter.prototype, {
this.setCurrentId(user.id);
},
getLastDomain: function() {
- return localStorage.last_domain;
+ var last_domain = BrowserStore.getItem("last_domain");
+ if (last_domain == null) {
+ last_domain = "";
+ }
+ return last_domain;
},
setLastDomain: function(domain) {
- localStorage.setItem("last_domain", domain);
+ BrowserStore.setItem("last_domain", domain);
},
getLastEmail: function() {
- return localStorage.last_email;
+ var last_email = BrowserStore.getItem("last_email");
+ if (last_email == null) {
+ last_email = "";
+ }
+ return last_email;
},
setLastEmail: function(email) {
- localStorage.setItem("last_email", email);
+ BrowserStore.setItem("last_email", email);
},
removeCurrentUser: function() {
this.setCurrentId(null);
@@ -144,89 +153,89 @@ var UserStore = assign({}, EventEmitter.prototype, {
this._storeProfiles(ps);
},
_storeProfiles: function(profiles) {
- sessionStorage.setItem("profiles", JSON.stringify(profiles));
+ BrowserStore.setGlobalItem("profiles", JSON.stringify(profiles));
var profileUsernameMap = {};
for (var id in profiles) {
profileUsernameMap[profiles[id].username] = profiles[id];
}
- sessionStorage.setItem("profileUsernameMap", JSON.stringify(profileUsernameMap));
+ BrowserStore.setGlobalItem("profileUsernameMap", JSON.stringify(profileUsernameMap));
},
_getProfiles: function() {
var profiles = {};
try {
- profiles = JSON.parse(sessionStorage.getItem("profiles"));
-
- if (profiles == null) {
- profiles = {};
- }
+ profiles = JSON.parse(BrowserStore.getGlobalItem("profiles"));
}
catch (err) {
}
+ if (profiles == null) {
+ profiles = {};
+ }
+
return profiles;
},
_getProfilesUsernameMap: function() {
var profileUsernameMap = {};
try {
- profileUsernameMap = JSON.parse(sessionStorage.getItem("profileUsernameMap"));
-
- if (profileUsernameMap == null) {
- profileUsernameMap = {};
- }
+ profileUsernameMap = JSON.parse(BrowserStore.getGlobalItem("profileUsernameMap"));
}
catch (err) {
}
+ if (profileUsernameMap == null) {
+ profileUsernameMap = {};
+ }
+
return profileUsernameMap;
},
setSessions: function(sessions) {
- sessionStorage.setItem("sessions", JSON.stringify(sessions));
+ BrowserStore.setItem("sessions", JSON.stringify(sessions));
},
getSessions: function() {
var sessions = [];
try {
- sessions = JSON.parse(sessionStorage.getItem("sessions"));
-
- if (sessions == null) {
- sessions = [];
- }
+ sessions = JSON.parse(BrowserStore.getItem("sessions"));
}
catch (err) {
- }
+ }
+ if (sessions == null) {
+ sessions = [];
+ }
return sessions;
},
setAudits: function(audits) {
- sessionStorage.setItem("audits", JSON.stringify(audits));
+ BrowserStore.setItem("audits", JSON.stringify(audits));
},
getAudits: function() {
var audits = [];
try {
- audits = JSON.parse(sessionStorage.getItem("audits"));
-
- if (audits == null) {
- audits = [];
- }
+ audits = JSON.parse(BrowserStore.getItem("audits"));
}
catch (err) {
- }
+ }
+
+ if (audits == null) {
+ audits = [];
+ }
return audits;
},
setTeams: function(teams) {
- sessionStorage.setItem("teams", JSON.stringify(teams));
+ BrowserStore.setItem("teams", JSON.stringify(teams));
},
getTeams: function() {
var teams = [];
try {
- teams = JSON.parse(sessionStorage.getItem("teams"));
+ teams = JSON.parse(BrowserStore.getItem("teams"));
- if (teams == null) {
- teams = [];
- }
}
catch (err) {
- }
+ }
+
+ if (teams == null) {
+ teams = [];
+ }
return teams;
},
@@ -249,17 +258,21 @@ var UserStore = assign({}, EventEmitter.prototype, {
}
},
getLastVersion: function() {
- return sessionStorage.last_version;
+ var last_version = BrowserStore.getItem("last_version");
+ if (last_version == null) {
+ last_version = "";
+ }
+ return last_version;
},
setLastVersion: function(version) {
- sessionStorage.setItem("last_version", version);
+ BrowserStore.setItem("last_version", version);
},
setStatuses: function(statuses) {
this._setStatuses(statuses);
this.emitStatusesChange();
},
_setStatuses: function(statuses) {
- sessionStorage.setItem("statuses", JSON.stringify(statuses));
+ BrowserStore.setItem("statuses", JSON.stringify(statuses));
},
setStatus: function(user_id, status) {
var statuses = this.getStatuses();
@@ -270,15 +283,15 @@ var UserStore = assign({}, EventEmitter.prototype, {
getStatuses: function() {
var statuses = {};
try {
- statuses = JSON.parse(sessionStorage.getItem("statuses"));
-
- if (statuses == null) {
- statuses = {};
- }
+ statuses = JSON.parse(BrowserStore.getItem("statuses"));
}
catch (err) {
}
+ if (statuses == null) {
+ statuses = {};
+ }
+
return statuses;
},
getStatus: function(id) {
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/client.jsx b/web/react/utils/client.jsx
index b4030baac..11d4c2601 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -1,6 +1,7 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
+var BrowserStore = require('../stores/browser_store.jsx');
module.exports.track = function(category, action, label, prop, val) {
global.window.snowplow('trackStructEvent', category, action, label, prop, val);
@@ -204,7 +205,7 @@ module.exports.resetPassword = function(data, success, error) {
module.exports.logout = function() {
module.exports.track('api', 'api_users_logout');
- sessionStorage.clear();
+ BrowserStore.clear();
window.location.href = "/logout";
};
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index f8a7d6450..5ded0e76f 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -8,7 +8,7 @@ var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
var AsyncClient = require('./async_client.jsx');
var client = require('./client.jsx');
-var LinkifyIt = require('linkify-it');
+var Autolinker = require('autolinker');
module.exports.isEmail = function(email) {
var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
@@ -97,20 +97,6 @@ module.exports.getCookie = function(name) {
if (parts.length == 2) return parts.pop().split(";").shift();
}
-module.exports.isLocalStorageSupported = function() {
- try {
- sessionStorage.setItem("testSession", '1');
- sessionStorage.removeItem("testSession");
-
- localStorage.setItem("testLocal", '1');
- localStorage.removeItem("testLocal", '1');
-
- return true;
- }
- catch (e) {
- return false;
- }
-}
module.exports.notifyMe = function(title, body, channel) {
if ("Notification" in window && Notification.permission !== 'denied') {
@@ -211,16 +197,31 @@ module.exports.getTimestamp = function() {
return Date.now();
}
+var testUrlMatch = function(text) {
+ var urlMatcher = new Autolinker.matchParser.MatchParser;
+ var result = [];
+ var replaceFn = function(match) {
+ var linkData = {};
+ var matchText = match.getMatchedText();
+
+ linkData.text = matchText;
+ linkData.link = matchText.trim().indexOf("http") !== 0 ? "http://" + matchText : matchText;
+
+ result.push(linkData);
+ }
+ urlMatcher.replace(text,replaceFn,this);
+ return result;
+}
+
module.exports.extractLinks = function(text) {
var repRegex = new RegExp("<br>", "g");
- var linkMatcher = new LinkifyIt();
- var matches = linkMatcher.match(text.replace(repRegex, "\n"));
+ var matches = testUrlMatch(text.replace(repRegex, "\n"));
- if (!matches) return { "links": null, "text": text };
+ if (!matches.length) return { "links": null, "text": text };
- var links = []
+ var links = [];
for (var i = 0; i < matches.length; i++) {
- links.push(matches[i].url)
+ links.push(matches[i].link);
}
return { "links": links, "text": text };
@@ -402,7 +403,6 @@ module.exports.textToJsx = function(text, options) {
var implicitKeywords = UserStore.getCurrentMentionKeys();
var lines = text.split("\n");
- var urlMatcher = new LinkifyIt();
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
var words = line.split(" ");
@@ -434,14 +434,14 @@ module.exports.textToJsx = function(text, options) {
}
inner.push(<span key={name+i+z+"_span"}>{prefix}<a className={mClass + highlightSearchClass + " mention-link"} key={name+i+z+"_link"} href="#" onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(name)}>@{name}</a>{suffix} </span>);
- } else if (urlMatcher.test(word)) {
- var match = urlMatcher.match(word)[0];
- var link = match.url;
+ } else if (testUrlMatch(word).length) {
+ var match = testUrlMatch(word)[0];
+ var link = match.link;
- var prefix = word.substring(0,word.indexOf(match.raw))
- var suffix = word.substring(word.indexOf(match.raw)+match.raw.length);
+ var prefix = word.substring(0,word.indexOf(match.text));
+ var suffix = word.substring(word.indexOf(match.text)+match.text.length);
- inner.push(<span key={word+i+z+"_span"}>{prefix}<a key={word+i+z+"_link"} className={"theme" + highlightSearchClass} target="_blank" href={link}>{match.raw}</a>{suffix} </span>);
+ inner.push(<span key={word+i+z+"_span"}>{prefix}<a key={word+i+z+"_link"} className={"theme" + highlightSearchClass} target="_blank" href={link}>{match.text}</a>{suffix} </span>);
} else if (trimWord.match(hashRegex)) {
var suffix = word.match(puncEndRegex);