summaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
author=Corey Hulen <corey@hulen.com>2015-07-21 12:26:27 -0800
committer=Corey Hulen <corey@hulen.com>2015-07-21 12:26:27 -0800
commit848eeeeef4ed277ceb00d3fc6c8a0303c0e72d09 (patch)
tree98390442e8052b8e02469e5ccf7596e22b7ea07f /web
parente87adce4a059b97a65198b326be6304abdf4e657 (diff)
parentbee26e3f2654ce7acce647818847480569236343 (diff)
downloadchat-848eeeeef4ed277ceb00d3fc6c8a0303c0e72d09.tar.gz
chat-848eeeeef4ed277ceb00d3fc6c8a0303c0e72d09.tar.bz2
chat-848eeeeef4ed277ceb00d3fc6c8a0303c0e72d09.zip
fixing merge conflicts
Diffstat (limited to 'web')
-rw-r--r--web/react/components/channel_header.jsx127
-rw-r--r--web/react/components/create_comment.jsx3
-rw-r--r--web/react/components/create_post.jsx2
-rw-r--r--web/react/components/delete_channel_modal.jsx2
-rw-r--r--web/react/components/delete_post_modal.jsx4
-rw-r--r--web/react/components/edit_channel_modal.jsx2
-rw-r--r--web/react/components/edit_post_modal.jsx7
-rw-r--r--web/react/components/get_link_modal.jsx2
-rw-r--r--web/react/components/loading_screen.jsx24
-rw-r--r--web/react/components/login.jsx130
-rw-r--r--web/react/components/member_list_item.jsx5
-rw-r--r--web/react/components/member_list_team.jsx12
-rw-r--r--web/react/components/mention.jsx14
-rw-r--r--web/react/components/mention_list.jsx146
-rw-r--r--web/react/components/more_channels.jsx15
-rw-r--r--web/react/components/msg_typing.jsx2
-rw-r--r--web/react/components/navbar.jsx231
-rw-r--r--web/react/components/new_channel.jsx8
-rw-r--r--web/react/components/password_reset.jsx79
-rw-r--r--web/react/components/post.jsx25
-rw-r--r--web/react/components/post_body.jsx17
-rw-r--r--web/react/components/post_info.jsx7
-rw-r--r--web/react/components/post_list.jsx128
-rw-r--r--web/react/components/post_right.jsx33
-rw-r--r--web/react/components/rename_channel_modal.jsx6
-rw-r--r--web/react/components/rename_team_modal.jsx8
-rw-r--r--web/react/components/search_bar.jsx39
-rw-r--r--web/react/components/search_results.jsx78
-rw-r--r--web/react/components/sidebar.jsx140
-rw-r--r--web/react/components/sidebar_header.jsx65
-rw-r--r--web/react/components/sidebar_right_menu.jsx4
-rw-r--r--web/react/components/signup_team.jsx11
-rw-r--r--web/react/components/signup_team_complete.jsx141
-rw-r--r--web/react/components/signup_user_complete.jsx45
-rw-r--r--web/react/components/team_members.jsx2
-rw-r--r--web/react/components/user_profile.jsx4
-rw-r--r--web/react/components/user_settings.jsx130
-rw-r--r--web/react/components/view_image.jsx9
-rw-r--r--web/react/pages/channel.jsx10
-rw-r--r--web/react/pages/home.jsx7
-rw-r--r--web/react/pages/login.jsx4
-rw-r--r--web/react/pages/password_reset.jsx4
-rw-r--r--web/react/pages/signup_team_complete.jsx4
-rw-r--r--web/react/stores/browser_store.jsx153
-rw-r--r--web/react/stores/channel_store.jsx117
-rw-r--r--web/react/stores/error_store.jsx11
-rw-r--r--web/react/stores/post_store.jsx65
-rw-r--r--web/react/stores/socket_store.jsx2
-rw-r--r--web/react/stores/team_store.jsx25
-rw-r--r--web/react/stores/user_store.jsx153
-rw-r--r--web/react/utils/client.jsx33
-rw-r--r--web/react/utils/constants.jsx2
-rw-r--r--web/react/utils/utils.jsx118
-rw-r--r--web/sass-files/sass/partials/_base.scss10
-rw-r--r--web/sass-files/sass/partials/_files.scss3
-rw-r--r--web/sass-files/sass/partials/_headers.scss61
-rw-r--r--web/sass-files/sass/partials/_loading.scss50
-rw-r--r--web/sass-files/sass/partials/_mentions.scss13
-rw-r--r--web/sass-files/sass/partials/_modal.scss3
-rw-r--r--web/sass-files/sass/partials/_navbar.scss1
-rw-r--r--web/sass-files/sass/partials/_popover.scss9
-rw-r--r--web/sass-files/sass/partials/_post.scss15
-rw-r--r--web/sass-files/sass/partials/_post_right.scss7
-rw-r--r--web/sass-files/sass/partials/_responsive.scss19
-rw-r--r--web/sass-files/sass/partials/_search.scss13
-rw-r--r--web/sass-files/sass/partials/_settings.scss4
-rw-r--r--web/sass-files/sass/partials/_sidebar--right.scss8
-rw-r--r--web/sass-files/sass/partials/_signup.scss4
-rw-r--r--web/sass-files/sass/partials/_variables.scss8
-rw-r--r--web/sass-files/sass/styles.scss3
-rw-r--r--web/static/config/config.js4
-rw-r--r--web/static/images/dropdown-icon.pngbin2877 -> 0 bytes
-rw-r--r--web/templates/channel.html2
-rw-r--r--web/templates/head.html6
-rw-r--r--web/templates/home.html2
-rw-r--r--web/templates/login.html4
-rw-r--r--web/templates/password_reset.html2
-rw-r--r--web/templates/signup_team.html3
-rw-r--r--web/templates/signup_team_complete.html2
-rw-r--r--web/templates/signup_team_confirm.html1
-rw-r--r--web/templates/signup_user_complete.html2
-rw-r--r--web/web.go145
82 files changed, 1319 insertions, 1500 deletions
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index 68de80228..30435dc08 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -1,6 +1,7 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
+
var ChannelStore = require('../stores/channel_store.jsx');
var UserStore = require('../stores/user_store.jsx');
var PostStore = require('../stores/post_store.jsx');
@@ -16,7 +17,7 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
-var ExtraMembers = React.createClass({
+var PopoverListMembers = React.createClass({
componentDidMount: function() {
var originalLeave = $.fn.popover.Constructor.prototype.leave;
$.fn.popover.Constructor.prototype.leave = function(obj) {
@@ -35,30 +36,29 @@ var ExtraMembers = React.createClass({
$("#member_popover").popover({placement : 'bottom', trigger: 'click', html: true});
$('body').on('click', function (e) {
- if ($(e.target.parentNode.parentNode)[0] !== $("#member_popover")[0] && $(e.target).parents('.popover.in').length === 0) {
+ if ($(e.target.parentNode.parentNode)[0] !== $("#member_popover")[0] && $(e.target).parents('.popover.in').length === 0) {
$("#member_popover").popover('hide');
}
});
-
},
+
render: function() {
- var count = this.props.members.length == 0 ? "-" : this.props.members.length;
- count = this.props.members.length > 19 ? "20+" : count;
- var data_content = "";
- var sortedMembers = this.props.members;
+ var popoverHtml = '';
+ var members = this.props.members;
+ var count = (members.length > 20) ? "20+" : (members.length || '-');
- if(sortedMembers) {
- sortedMembers.sort(function(a,b) {
+ if (members) {
+ members.sort(function(a,b) {
return a.username.localeCompare(b.username);
- })
+ });
- sortedMembers.forEach(function(m) {
- data_content += "<div style='white-space: nowrap'>" + m.username + "</div>";
+ members.forEach(function(m) {
+ popoverHtml += "<div class='text--nowrap'>" + m.username + "</div>";
});
}
return (
- <div style={{"cursor" : "pointer"}} id="member_popover" data-toggle="popover" data-content={data_content} data-original-title="Members" >
+ <div id="member_popover" data-toggle="popover" data-content={popoverHtml} data-original-title="Members" >
<div id="member_tooltip" data-toggle="tooltip" title="View Channel Members">
{count} <span className="glyphicon glyphicon-user" aria-hidden="true"></span>
</div>
@@ -78,6 +78,7 @@ function getStateFromStores() {
}
module.exports = React.createClass({
+ displayName: 'ChannelHeader',
componentDidMount: function() {
ChannelStore.addChangeListener(this._onChange);
ChannelStore.addExtraInfoChangeListener(this._onChange);
@@ -99,7 +100,7 @@ module.exports = React.createClass({
$(".channel-header__info .description").popover({placement : 'bottom', trigger: 'hover', html: true, delay: {show: 500, hide: 500}});
},
_onSocketChange: function(msg) {
- if(msg.action === "new_user") {
+ if (msg.action === "new_user") {
AsyncClient.getChannelExtraInfo(true);
}
},
@@ -107,15 +108,14 @@ module.exports = React.createClass({
return getStateFromStores();
},
handleLeave: function(e) {
- var self = this;
Client.leaveChannel(this.state.channel.id,
function(data) {
var townsquare = ChannelStore.getByName('town-square');
utils.switchChannel(townsquare);
- }.bind(this),
+ },
function(err) {
AsyncClient.dispatchError(err, "handleLeave");
- }.bind(this)
+ }
);
},
searchMentions: function(e) {
@@ -131,52 +131,29 @@ module.exports = React.createClass({
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_SEARCH_TERM,
term: terms,
- do_search: false
+ do_search: true,
+ is_mention_search: true
});
-
- Client.search(
- terms,
- function(data) {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECIEVED_SEARCH,
- results: data,
- is_mention_search: true
- });
- },
- function(err) {
- dispatchError(err, "search");
- }
- );
},
+
render: function() {
if (this.state.channel == null) {
- return (
- <div></div>
- );
+ return null;
}
- var description = utils.textToJsx(this.state.channel.description, {"singleline": true, "noMentionHighlight": true});
- var popoverContent = React.renderToString(<MessageWrapper message={this.state.channel.description}/>);
- var channelTitle = "";
- var channelName = this.state.channel.name;
+ var channel = this.state.channel;
+ var description = utils.textToJsx(channel.description, {"singleline": true, "noMentionHighlight": true});
+ var popoverContent = React.renderToString(<MessageWrapper message={channel.description}/>);
+ var channelTitle = channel.display_name;
var currentId = UserStore.getCurrentId();
var isAdmin = this.state.memberChannel.roles.indexOf("admin") > -1 || this.state.memberTeam.roles.indexOf("admin") > -1;
- var searchForm = <th className="search-bar__container"><NavbarSearchBox /></th>;
- var isDirect = false;
-
- if (this.state.channel.type === 'O') {
- channelTitle = this.state.channel.display_name;
- } else if (this.state.channel.type === 'P') {
- channelTitle = this.state.channel.display_name;
- } else if (this.state.channel.type === 'D') {
- isDirect = true;
+ var isDirect = (this.state.channel.type === 'D');
+
+ if (isDirect) {
if (this.state.users.length > 1) {
- if (this.state.users[0].id === UserStore.getCurrentId()) {
- channelTitle = <UserProfile userId={this.state.users[1].id} overwriteName={this.state.users[1].full_name ? this.state.users[1].full_name : this.state.users[1].username} />;
- } else {
- channelTitle = <UserProfile userId={this.state.users[0].id} overwriteName={this.state.users[0].full_name ? this.state.users[0].full_name : this.state.users[0].username} />;
- }
+ var contact = this.state.users[((this.state.users[0].id === currentId) ? 1 : 0)];
+ channelTitle = <UserProfile userId={contact.id} overwriteName={contact.nickname || contact.username} />;
}
}
@@ -192,25 +169,28 @@ module.exports = React.createClass({
<span className="glyphicon glyphicon-chevron-down header-dropdown__icon"></span>
</a>
<ul className="dropdown-menu" role="menu" aria-labelledby="channel_header_dropdown">
- <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_info" data-channelid={this.state.channel.id} href="#">View Info</a></li>
- <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_invite" href="#">Add Members</a></li>
- { isAdmin ?
+ <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_info" data-channelid={channel.id} href="#">View Info</a></li>
+ { !ChannelStore.isDefault(channel) ?
+ <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_invite" href="#">Add Members</a></li>
+ : null
+ }
+ { isAdmin && !ChannelStore.isDefault(channel) ?
<li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_members" href="#">Manage Members</a></li>
- : ""
+ : null
}
- <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#edit_channel" data-desc={this.state.channel.description} data-title={this.state.channel.display_name} data-channelid={this.state.channel.id}>Set Channel Description...</a></li>
- <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#channel_notifications" data-title={this.state.channel.display_name} data-channelid={this.state.channel.id}>Notification Preferences</a></li>
- { isAdmin && channelName != Constants.DEFAULT_CHANNEL ?
- <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#rename_channel" data-display={this.state.channel.display_name} data-name={this.state.channel.name} data-channelid={this.state.channel.id}>Rename Channel...</a></li>
- : ""
+ <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#edit_channel" data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set Channel Description...</a></li>
+ <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#channel_notifications" data-title={channel.display_name} data-channelid={channel.id}>Notification Preferences</a></li>
+ { isAdmin && !ChannelStore.isDefault(channel) ?
+ <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#rename_channel" data-display={channel.display_name} data-name={channel.name} data-channelid={channel.id}>Rename Channel...</a></li>
+ : null
}
- { isAdmin && channelName != Constants.DEFAULT_CHANNEL ?
- <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#delete_channel" data-title={this.state.channel.display_name} data-channelid={this.state.channel.id}>Delete Channel...</a></li>
- : ""
+ { isAdmin && !ChannelStore.isDefault(channel) ?
+ <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#delete_channel" data-title={channel.display_name} data-channelid={channel.id}>Delete Channel...</a></li>
+ : null
}
- { channelName != Constants.DEFAULT_CHANNEL ?
+ { !ChannelStore.isDefault(channel) ?
<li role="presentation"><a role="menuitem" href="#" onClick={this.handleLeave}>Leave Channel</a></li>
- : ""
+ : null
}
</ul>
</div>
@@ -220,14 +200,13 @@ module.exports = React.createClass({
<a href="#"><strong className="heading">{channelTitle}</strong></a>
}
</th>
- <th><ExtraMembers members={this.state.users} channelId={this.state.channel.id} /></th>
- { searchForm }
+ <th><PopoverListMembers members={this.state.users} channelId={channel.id} /></th>
+ <th className="search-bar__container"><NavbarSearchBox /></th>
<th>
- <div className="dropdown" style={{"marginLeft":"5px", "marginRight":"10px"}}>
+ <div className="dropdown channel-header__links">
<a href="#" className="dropdown-toggle theme" type="button" id="channel_header_right_dropdown" data-toggle="dropdown" aria-expanded="true">
- <i className="fa fa-caret-down"></i>
- </a>
- <ul className="dropdown-menu" role="menu" aria-labelledby="channel_header_right_dropdown" style={{"left": "-150px"}}>
+ <span dangerouslySetInnerHTML={{__html: " <svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='4px' height='16px' viewBox='0 0 8 32' enable-background='new 0 0 8 32' xml:space='preserve'> <g> <circle cx='4' cy='4.062' r='4'/> <circle cx='4' cy='16' r='4'/> <circle cx='4' cy='28' r='4'/> </g> </svg>"}} /> </a>
+ <ul className="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="channel_header_right_dropdown">
<li role="presentation"><a role="menuitem" href="#" onClick={this.searchMentions}>Recent Mentions</a></li>
</ul>
</div>
@@ -237,5 +216,3 @@ module.exports = React.createClass({
);
}
});
-
-
diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx
index 9e3feb25c..6ed0f0b34 100644
--- a/web/react/components/create_comment.jsx
+++ b/web/react/components/create_comment.jsx
@@ -43,7 +43,7 @@ module.exports = React.createClass({
client.createPost(post, ChannelStore.getCurrent(),
function(data) {
- this.setState({ messageText: '', submitting: false, post_error: null });
+ this.setState({ messageText: '', submitting: false, post_error: null, server_error: null });
this.clearPreviews();
AsyncClient.getPosts(true, this.props.channelId);
@@ -57,6 +57,7 @@ module.exports = React.createClass({
function(err) {
var state = {};
state.server_error = err.message;
+ state.submitting = false;
if (err.message === "Invalid RootId parameter") {
if ($('#post_deleted').length > 0) $('#post_deleted').modal('show');
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index 0c23dcfac..d38a6798f 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -234,7 +234,7 @@ 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 server_error = this.state.server_error ? <div className='has-error'><label className='control-label'>{ this.state.server_error }</label></div> : null;
var post_error = this.state.post_error ? <label className='control-label'>{this.state.post_error}</label> : null;
var limit_error = this.state.limit_error ? <div className='has-error'><label className='control-label'>{this.state.limit_error}</label></div> : null;
diff --git a/web/react/components/delete_channel_modal.jsx b/web/react/components/delete_channel_modal.jsx
index a8c690789..e23a37740 100644
--- a/web/react/components/delete_channel_modal.jsx
+++ b/web/react/components/delete_channel_modal.jsx
@@ -12,7 +12,7 @@ module.exports = React.createClass({
Client.deleteChannel(this.state.channel_id,
function(data) {
AsyncClient.getChannels(true);
- window.location.href = '/channels/town-square';
+ window.location.href = '/';
}.bind(this),
function(err) {
AsyncClient.dispatchError(err, "handleDelete");
diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx
index fefac12d7..11970bc2b 100644
--- a/web/react/components/delete_post_modal.jsx
+++ b/web/react/components/delete_post_modal.jsx
@@ -56,13 +56,13 @@ module.exports = React.createClass({
$(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
var newState = {};
if(BrowserStore.getItem('edit_state_transfer')) {
- newState = JSON.parse(BrowserStore.getItem('edit_state_transfer'));
+ newState = BrowserStore.getItem('edit_state_transfer');
BrowserStore.removeItem('edit_state_transfer');
} else {
var button = e.relatedTarget;
newState = { title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments') };
}
- self.setState(newState)
+ self.setState(newState);
});
PostStore.addSelectedPostChangeListener(this._onChange);
},
diff --git a/web/react/components/edit_channel_modal.jsx b/web/react/components/edit_channel_modal.jsx
index 255654fd5..c0818959a 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" style={{resize: "none"}} rows="6" ref="channelDesc" maxLength="1024" value={this.state.description} onChange={this.handleUserInput}></textarea>
+ <textarea className="form-control no-resize" 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 d741e189b..21b75bb6e 100644
--- a/web/react/components/edit_post_modal.jsx
+++ b/web/react/components/edit_post_modal.jsx
@@ -4,6 +4,7 @@
var Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var Textbox = require('./textbox.jsx');
+var BrowserStore = require('../stores/browser_store.jsx');
module.exports = React.createClass({
handleEdit: function(e) {
@@ -13,14 +14,14 @@ module.exports = React.createClass({
if (updatedPost.message.length === 0) {
var tempState = this.state;
delete tempState.editText;
- BrowserStore.setItem('edit_state_transfer', JSON.stringify(tempState));
+ BrowserStore.setItem('edit_state_transfer', tempState);
$("#edit_post").modal('hide');
$("#delete_post").modal('show');
return;
}
- updatedPost.id = this.state.post_id
- updatedPost.channel_id = this.state.channel_id
+ updatedPost.id = this.state.post_id;
+ updatedPost.channel_id = this.state.channel_id;
Client.updatePost(updatedPost,
function(data) {
diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx
index bbfdce63a..af5314e64 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" style={{resize: "none"}} readOnly="true" value={this.state.value}></textarea>
+ <textarea className="form-control no-resize" 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/loading_screen.jsx b/web/react/components/loading_screen.jsx
new file mode 100644
index 000000000..5905e519b
--- /dev/null
+++ b/web/react/components/loading_screen.jsx
@@ -0,0 +1,24 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+module.exports = React.createClass({
+ displayName: "LoadingScreen",
+ propTypes: {
+ position: React.PropTypes.oneOf(['absolute', 'fixed', 'relative', 'static', 'inherit'])
+ },
+ getDefaultProps: function() {
+ return { position: 'relative' };
+ },
+ render: function() {
+ return (
+ <div className="loading-screen" style={{position: this.props.position}}>
+ <div className="loading__content">
+ <h3>Loading</h3>
+ <div className="round round-1"></div>
+ <div className="round round-2"></div>
+ <div className="round round-3"></div>
+ </div>
+ </div>
+ );
+ }
+});
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index 74c7d4065..71fefff5b 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -1,102 +1,21 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-
-
-
var utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx');
var UserStore = require('../stores/user_store.jsx');
+var TeamStore = require('../stores/team_store.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
-
-
-var FindTeamDomain = React.createClass({
- handleSubmit: function(e) {
- e.preventDefault();
- var state = { }
-
- var domain = this.refs.domain.getDOMNode().value.trim();
- if (!domain) {
- state.server_error = "A domain is required"
- this.setState(state);
- return;
- }
-
- 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;
- }
-
- state.server_error = "";
- this.setState(state);
-
- client.findTeamByDomain(domain,
- function(data) {
- console.log(data);
- if (data) {
- window.location.href = window.location.protocol + "//" + domain + "." + utils.getDomainWithOutSub();
- }
- else {
- this.state.server_error = "We couldn't find your " + strings.Team + ".";
- this.setState(this.state);
- }
- }.bind(this),
- function(err) {
- this.state.server_error = err.message;
- this.setState(this.state);
- }.bind(this)
- );
- },
- getInitialState: function() {
- return { };
- },
- render: function() {
- var server_error = this.state.server_error ? <label className="control-label">{this.state.server_error}</label> : null;
-
- return (
- <div className="signup-team__container">
- <div>
- <span className="signup-team__name">{ config.SiteName }</span>
- <br/>
- <span className="signup-team__subdomain">Enter your {strings.Team}'s domain.</span>
- <br/>
- <br/>
- </div>
- <form onSubmit={this.handleSubmit}>
- <div className={server_error ? 'form-group has-error' : 'form-group'}>
- { server_error }
- <input type="text" className="form-control" name="domain" ref="domain" placeholder="team domain" />
- </div>
- <div className="form-group">
- <button type="submit" className="btn btn-primary">Continue</button>
- </div>
- <div>
- <span>Don't remember your {strings.Team}'s domain? <a href="/find_team">Find it here</a></span>
- </div>
- <br/>
- <br/>
- <br/>
- <br/>
- <br/>
- <br/>
- <div>
- <span>{"Want to create your own " + strings.Team + "?"} <a href={utils.getHomeLink()} className="signup-team-login">Sign up now</a></span>
- </div>
- </form>
- </div>
- );
- }
-});
+var Constants = require('../utils/constants.jsx');
module.exports = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
var state = { }
- var domain = this.refs.domain.getDOMNode().value.trim();
- if (!domain) {
- state.server_error = "A domain is required"
+ var name = this.props.teamName
+ if (!name) {
+ state.server_error = "Bad team name"
this.setState(state);
return;
}
@@ -124,23 +43,22 @@ module.exports = React.createClass({
state.server_error = "";
this.setState(state);
- client.loginByEmail(domain, email, password,
+ client.loginByEmail(name, email, password,
function(data) {
- UserStore.setLastDomain(domain);
- UserStore.setLastEmail(email);
UserStore.setCurrentUser(data);
+ UserStore.setLastEmail(email);
var redirect = utils.getUrlParameter("redirect");
if (redirect) {
- window.location.href = decodeURI(redirect);
+ window.location.pathname = decodeURI(redirect);
} else {
- window.location.href = '/channels/town-square';
+ window.location.pathname = '/' + name + '/channels/town-square';
}
}.bind(this),
function(err) {
if (err.message == "Login failed because email address has not been verified") {
- window.location.href = '/verify?domain=' + encodeURIComponent(domain) + '&email=' + encodeURIComponent(email);
+ window.location.href = '/verify_email?name=' + encodeURIComponent(name) + '&email=' + encodeURIComponent(email);
return;
}
state.server_error = err.message;
@@ -161,35 +79,35 @@ module.exports = React.createClass({
priorEmail = decodeURIComponent(emailParam);
}
- var subDomainClass = "form-control hidden";
- var subDomain = utils.getSubDomain();
+ var teamDisplayName = this.props.teamDisplayName;
+ var teamName = this.props.teamName;
- if (utils.isTestDomain()) {
- subDomainClass = "form-control";
- subDomain = UserStore.getLastDomain();
- } else if (subDomain == "") {
- return (<FindTeamDomain />);
+ var focusEmail = false;
+ var focusPassword = false;
+ if (priorEmail != "") {
+ focusPassword = true;
+ } else {
+ focusEmail = true;
}
return (
<div className="signup-team__container">
<div>
- <span className="signup-team__name">{ subDomain }</span>
+ <span className="signup-team__name">{ teamDisplayName }</span>
<br/>
- <span className="signup-team__subdomain">{ utils.getDomainWithOutSub() }</span>
+ <span className="signup-team__subdomain">/{ teamName }/</span>
<br/>
<br/>
</div>
<form onSubmit={this.handleSubmit}>
<div className={server_error ? 'form-group has-error' : 'form-group'}>
{ server_error }
- <input type="text" className={subDomainClass} name="domain" defaultValue={subDomain} ref="domain" placeholder="Domain" />
</div>
<div className={server_error ? 'form-group has-error' : 'form-group'}>
- <input type="email" className="form-control" name="email" defaultValue={priorEmail} ref="email" placeholder="Email" />
+ <input autoFocus={focusEmail} type="email" className="form-control" name="email" defaultValue={priorEmail} ref="email" placeholder="Email" />
</div>
<div className={server_error ? 'form-group has-error' : 'form-group'}>
- <input type="password" className="form-control" name="password" ref="password" placeholder="Password" />
+ <input autoFocus={focusPassword} type="password" className="form-control" name="password" ref="password" placeholder="Password" />
</div>
<div className="form-group">
<button type="submit" className="btn btn-primary">Sign in</button>
@@ -198,10 +116,10 @@ module.exports = React.createClass({
<span><a href="/find_team">{"Find other " + strings.TeamPlural}</a></span>
</div>
<div className="form-group">
- <a href="/reset_password">I forgot my password</a>
+ <a href={"/" + teamName + "/reset_password"}>I forgot my password</a>
</div>
<div className="external-link">
- <span>{"Want to create your own " + strings.Team + "?"} <a href={utils.getHomeLink()} className="signup-team-login">Sign up now</a></span>
+ <span>{"Want to create your own " + strings.Team + "?"} <a href="/" className="signup-team-login">Sign up now</a></span>
</div>
</form>
</div>
diff --git a/web/react/components/member_list_item.jsx b/web/react/components/member_list_item.jsx
index 357fd49a8..a5279909b 100644
--- a/web/react/components/member_list_item.jsx
+++ b/web/react/components/member_list_item.jsx
@@ -23,6 +23,7 @@ module.exports = React.createClass({
var member = this.props.member;
var isAdmin = this.props.isAdmin;
var isMemberAdmin = member.roles.indexOf("admin") > -1;
+ var timestamp = UserStore.getCurrentUser().update_at;
var invite;
if (member.invited && this.props.handleInvite) {
@@ -48,12 +49,12 @@ module.exports = React.createClass({
</div>
);
} else {
- invite = <div className="member-role text-capitalize" style={{marginRight: 15}}>{member.roles || 'Member'}</div>;
+ invite = <div className="member-role text-capitalize">{member.roles || 'Member'}<span className="caret hidden"></span></div>;
}
return (
<div className="row member-div">
- <img className="post-profile-img pull-left" src={"/api/v1/users/" + member.id + "/image"} height="36" width="36" />
+ <img className="post-profile-img pull-left" src={"/api/v1/users/" + member.id + "/image?time=" + timestamp} height="36" width="36" />
<span className="member-name">{member.username}</span>
<span className="member-email">{member.email}</span>
{ invite }
diff --git a/web/react/components/member_list_team.jsx b/web/react/components/member_list_team.jsx
index cfb473e5e..cb48e5cc5 100644
--- a/web/react/components/member_list_team.jsx
+++ b/web/react/components/member_list_team.jsx
@@ -5,6 +5,7 @@ var ChannelStore = require('../stores/channel_store.jsx');
var UserStore = require('../stores/user_store.jsx');
var Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
+var utils = require('../utils/utils.jsx');
var MemberListTeamItem = React.createClass({
handleMakeMember: function() {
@@ -59,9 +60,10 @@ var MemberListTeamItem = React.createClass({
return {};
},
render: function() {
- var server_error = this.state.server_error ? <div style={{ clear: "both" }} className="has-error"><label className='has-error control-label'>{this.state.server_error}</label></div> : null;
+ var server_error = this.state.server_error ? <div className="has-error"><label className='has-error control-label'>{this.state.server_error}</label></div> : null;
var user = this.props.user;
- var currentRoles = "Member"
+ var currentRoles = "Member";
+ var timestamp = UserStore.getCurrentUser().update_at;
if (user.roles.length > 0) {
currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1);
@@ -83,9 +85,9 @@ var MemberListTeamItem = React.createClass({
return (
<div className="row member-div">
- <img className="post-profile-img pull-left" src={"/api/v1/users/" + user.id + "/image"} height="36" width="36" />
- <span className="member-name">{user.full_name.trim() ? user.full_name : user.username}</span>
- <span className="member-email">{user.full_name.trim() ? user.username : email}</span>
+ <img className="post-profile-img pull-left" src={"/api/v1/users/" + user.id + "/image?time=" + timestamp} height="36" width="36" />
+ <span className="member-name">{utils.getDisplayName(user)}</span>
+ <span className="member-email">{email}</span>
<div className="dropdown member-drop">
<a href="#" className="dropdown-toggle theme" type="button" id="channel_header_dropdown" data-toggle="dropdown" aria-expanded="true">
<span>{currentRoles} </span>
diff --git a/web/react/components/mention.jsx b/web/react/components/mention.jsx
index 3c33ddf49..114dc183f 100644
--- a/web/react/components/mention.jsx
+++ b/web/react/components/mention.jsx
@@ -1,19 +1,27 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
+var UserStore = require("../stores/user_store.jsx");
module.exports = React.createClass({
handleClick: function() {
this.props.handleClick(this.props.username);
},
+ getInitialState: function() {
+ return null;
+ },
render: function() {
+ var self = this;
var icon;
- if (this.props.id != null) {
- icon = <span><img className="mention-img" src={"/api/v1/users/" + this.props.id + "/image"}/></span>;
+ var timestamp = UserStore.getCurrentUser().update_at;
+ if (this.props.id === "allmention" || this.props.id === "channelmention") {
+ icon = <span><i className="mention-img fa fa-users fa-2x"></i></span>;
+ } else if (this.props.id != null) {
+ icon = <span><img className="mention-img" src={"/api/v1/users/" + this.props.id + "/image?time=" + timestamp}/></span>;
} else {
icon = <span><i className="mention-img fa fa-users fa-2x"></i></span>;
}
return (
- <div className="mentions-name" onClick={this.handleClick}>
+ <div className={"mentions-name " + this.props.isFocused} id={this.props.id + "_mentions"} onClick={this.handleClick} onMouseEnter={this.props.handleMouseEnter}>
<div className="pull-left">{icon}</div>
<div className="pull-left mention-align"><span>@{this.props.username}</span><span className="mention-fullname">{this.props.secondary_text}</span></div>
</div>
diff --git a/web/react/components/mention_list.jsx b/web/react/components/mention_list.jsx
index ba2c53612..71a6083d2 100644
--- a/web/react/components/mention_list.jsx
+++ b/web/react/components/mention_list.jsx
@@ -7,19 +7,48 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Mention = require('./mention.jsx');
var Constants = require('../utils/constants.jsx');
+var Utils = require('../utils/utils.jsx');
var ActionTypes = Constants.ActionTypes;
+var MAX_HEIGHT_LIST = 292;
+var MAX_ITEMS_IN_LIST = 25;
+var ITEM_HEIGHT = 36;
+
module.exports = React.createClass({
+ displayName: "MentionList",
componentDidMount: function() {
PostStore.addMentionDataChangeListener(this._onChange);
-
var self = this;
- $('body').on('keypress.mentionlist', '#'+this.props.id,
+
+ $('body').on('keydown.mentionlist', '#'+this.props.id,
function(e) {
- if (!self.isEmpty() && self.state.mentionText != '-1' && e.which === 13) {
+ if (!self.isEmpty() && self.state.mentionText != '-1' && (e.which === 13 || e.which === 9)) {
e.stopPropagation();
e.preventDefault();
- self.addFirstMention();
+ self.addCurrentMention();
+ }
+ else if (!self.isEmpty() && self.state.mentionText != '-1' && (e.which === 38 || e.which === 40)) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ var tempSelectedMention = -1;
+ if (e.which === 38) {
+ if (self.getSelection(self.state.selectedMention - 1))
+ self.setState({ selectedMention: self.state.selectedMention - 1, selectedUsername: self.refs['mention' + (self.state.selectedMention - 1)].props.username });
+ else {
+ while (self.getSelection(++tempSelectedMention))
+ ; //Need to find the top of the list
+ self.setState({ selectedMention: tempSelectedMention - 1, selectedUsername: self.refs['mention' + (tempSelectedMention - 1)].props.username });
+ }
+ }
+ else if (e.which === 40) {
+ if (self.getSelection(self.state.selectedMention + 1))
+ self.setState({ selectedMention: self.state.selectedMention + 1, selectedUsername: self.refs['mention' + (self.state.selectedMention + 1)].props.username });
+ else
+ self.setState({ selectedMention: 0, selectedUsername: self.refs.mention0.props.username });
+ }
+
+ self.scrollToMention(e.which, tempSelectedMention);
}
}
);
@@ -32,7 +61,28 @@ module.exports = React.createClass({
},
componentWillUnmount: function() {
PostStore.removeMentionDataChangeListener(this._onChange);
- $('body').off('keypress.mentionlist', '#'+this.props.id);
+ $('body').off('keydown.mentionlist', '#'+this.props.id);
+ },
+ componentDidUpdate: function() {
+ if (this.state.mentionText != "-1") {
+ if (this.state.selectedUsername !== "" && (!this.getSelection(this.state.selectedMention) || this.state.selectedUsername !== this.refs['mention' + this.state.selectedMention].props.username)) {
+ var tempSelectedMention = -1;
+ var foundMatch = false;
+ while (tempSelectedMention < this.state.selectedMention && this.getSelection(++tempSelectedMention)) {
+ if (this.state.selectedUsername === this.refs['mention' + tempSelectedMention].props.username) {
+ this.setState({ selectedMention: tempSelectedMention });
+ foundMatch = true;
+ break;
+ }
+ }
+ if (this.getSelection(0) && !foundMatch) {
+ this.setState({ selectedMention: 0, selectedUsername: this.refs.mention0.props.username });
+ }
+ }
+ }
+ else if (this.state.selectedMention !== 0) {
+ this.setState({ selectedMention: 0, selectedUsername: "" });
+ }
},
_onChange: function(id, mentionText, excludeList) {
if (id !== this.props.id) return;
@@ -40,6 +90,7 @@ module.exports = React.createClass({
var newState = this.state;
if (mentionText != null) newState.mentionText = mentionText;
if (excludeList != null) newState.excludeUsers = excludeList;
+
this.setState(newState);
},
handleClick: function(name) {
@@ -51,6 +102,21 @@ module.exports = React.createClass({
this.setState({ mentionText: '-1' });
},
+ handleMouseEnter: function(listId) {
+ this.setState({ selectedMention: listId, selectedUsername: this.refs['mention' + listId].props.username });
+ },
+ getSelection: function(listId) {
+ if (!this.refs['mention' + listId])
+ return false;
+ else
+ return true;
+ },
+ addCurrentMention: function() {
+ if (!this.getSelection(this.state.selectedMention))
+ this.addFirstMention();
+ else
+ this.refs['mention' + this.state.selectedMention].handleClick();
+ },
addFirstMention: function() {
if (!this.refs.mention0) return;
this.refs.mention0.handleClick();
@@ -58,6 +124,23 @@ module.exports = React.createClass({
isEmpty: function() {
return (!this.refs.mention0);
},
+ scrollToMention: function(keyPressed, ifLoopUp) {
+ var direction = keyPressed === 38 ? "up" : "down";
+ var scrollAmount = 0;
+
+ if (direction === "up" && ifLoopUp !== -1)
+ scrollAmount = $("#mentionsbox").height() * 100; //Makes sure that it scrolls all the way to the bottom
+ else if (direction === "down" && this.state.selectedMention === 0)
+ scrollAmount = 0;
+ else if (direction === "up")
+ scrollAmount = "-=" + ($('#'+this.refs['mention' + this.state.selectedMention].props.id +"_mentions").innerHeight() - 5);
+ else if (direction === "down")
+ scrollAmount = "+=" + ($('#'+this.refs['mention' + this.state.selectedMention].props.id +"_mentions").innerHeight() - 5);
+
+ $("#mentionsbox").animate({
+ scrollTop: scrollAmount
+ }, 75);
+ },
alreadyMentioned: function(username) {
var excludeUsers = this.state.excludeUsers;
for (var i = 0; i < excludeUsers.length; i++) {
@@ -68,11 +151,12 @@ module.exports = React.createClass({
return false;
},
getInitialState: function() {
- return { excludeUsers: [], mentionText: "-1" };
+ return { excludeUsers: [], mentionText: "-1", selectedMention: 0, selectedUsername: "" };
},
render: function() {
+ var self = this;
var mentionText = this.state.mentionText;
- if (mentionText === '-1') return (<div/>);
+ if (mentionText === '-1') return null;
var profiles = UserStore.getActiveOnlyProfiles();
var users = [];
@@ -82,14 +166,16 @@ module.exports = React.createClass({
var all = {};
all.username = "all";
- all.full_name = "";
+ all.nickname = "";
all.secondary_text = "Notifies everyone in the team";
+ all.id = "allmention";
users.push(all);
var channel = {};
channel.username = "channel";
- channel.full_name = "";
+ channel.nickname = "";
channel.secondary_text = "Notifies everyone in the channel";
+ channel.id = "channelmention";
users.push(channel);
users.sort(function(a,b) {
@@ -100,44 +186,42 @@ module.exports = React.createClass({
var mentions = {};
var index = 0;
- for (var i = 0; i < users.length; i++) {
- if (Object.keys(mentions).length >= 25) break;
+ for (var i = 0; i < users.length && index < MAX_ITEMS_IN_LIST; i++) {
if (this.alreadyMentioned(users[i].username)) continue;
- var firstName = "", lastName = "";
- if (users[i].full_name.length > 0) {
- var splitName = users[i].full_name.split(' ');
- firstName = splitName[0].toLowerCase();
- lastName = splitName.length > 1 ? splitName[splitName.length-1].toLowerCase() : "";
- users[i].secondary_text = users[i].full_name;
- }
-
- if (firstName.lastIndexOf(mentionText,0) === 0
- || lastName.lastIndexOf(mentionText,0) === 0 || users[i].username.lastIndexOf(mentionText,0) === 0) {
- mentions[i+1] = (
+ if ((users[i].first_name && users[i].first_name.lastIndexOf(mentionText,0) === 0)
+ || (users[i].last_name && users[i].last_name.lastIndexOf(mentionText,0) === 0) || users[i].username.lastIndexOf(mentionText,0) === 0) {
+ mentions[index] = (
<Mention
ref={'mention' + index}
username={users[i].username}
- secondary_text={users[i].secondary_text}
+ secondary_text={Utils.getFullName(users[i])}
id={users[i].id}
+ listId={index}
+ isFocused={this.state.selectedMention === index ? "mentions-focus" : ""}
+ handleMouseEnter={function(value) { return function() { self.handleMouseEnter(value); } }(index)}
handleClick={this.handleClick} />
);
index++;
}
}
+
var numMentions = Object.keys(mentions).length;
- if (numMentions < 1) return (<div/>);
+ if (numMentions < 1) return null;
- 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;
- var max_height = $('#'+this.props.id).offset().top - 10;
+ var $mention_tab = $('#'+this.props.id);
+ var maxHeight = Math.min(MAX_HEIGHT_LIST, $mention_tab.offset().top - 10);
+ var style = {
+ height: Math.min(maxHeight, (numMentions*ITEM_HEIGHT) + 4),
+ width: $mention_tab.parent().width(),
+ bottom: $(window).height() - $mention_tab.offset().top,
+ left: $mention_tab.offset().left
+ };
return (
- <div className="mentions--top" style={{height: height, width: width, bottom: bottom, left: left}}>
- <div ref="mentionlist" className="mentions-box" style={{height: height, width: width}}>
+ <div className="mentions--top" style={style}>
+ <div ref="mentionlist" className="mentions-box" id="mentionsbox">
{ mentions }
</div>
</div>
diff --git a/web/react/components/more_channels.jsx b/web/react/components/more_channels.jsx
index c3ddc76f3..007476f9b 100644
--- a/web/react/components/more_channels.jsx
+++ b/web/react/components/more_channels.jsx
@@ -7,6 +7,7 @@ var client = require('../utils/client.jsx');
var asyncClient = require('../utils/async_client.jsx');
var UserStore = require('../stores/user_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
+var LoadingScreen = require('./loading_screen.jsx');
function getStateFromStores() {
return {
@@ -16,6 +17,8 @@ function getStateFromStores() {
}
module.exports = React.createClass({
+ displayName: "MoreChannelsModal",
+
componentDidMount: function() {
ChannelStore.addMoreChangeListener(this._onChange);
$(this.refs.modal.getDOMNode()).on('shown.bs.modal', function (e) {
@@ -90,7 +93,7 @@ module.exports = React.createClass({
<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>
+ <td className="td--action"><button onClick={outter.handleJoin.bind(outter, channel.id)} className="btn btn-primary">Join</button></td>
</tr>
)
})}
@@ -100,15 +103,7 @@ module.exports = React.createClass({
<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>
- }
+ : <LoadingScreen /> }
{ server_error }
</div>
<div className="modal-footer">
diff --git a/web/react/components/msg_typing.jsx b/web/react/components/msg_typing.jsx
index a6953028f..aacb315dd 100644
--- a/web/react/components/msg_typing.jsx
+++ b/web/react/components/msg_typing.jsx
@@ -51,7 +51,7 @@ module.exports = React.createClass({
},
render: function() {
return (
- <span className="msg-typing">{ this.state.text }</span>
+ <span className="msg-typing">{ this.state.text }</span>
);
}
});
diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx
index 35f7d9044..34c65c34f 100644
--- a/web/react/components/navbar.jsx
+++ b/web/react/components/navbar.jsx
@@ -2,21 +2,21 @@
// See License.txt for license information.
-var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
-var Sidebar = require('./sidebar.jsx');
var UserStore = require('../stores/user_store.jsx');
-var SocketStore = require('../stores/socket_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
-var Constants = require('../utils/constants.jsx');
+var TeamStore = require('../stores/team_store.jsx');
+
var UserProfile = require('./user_profile.jsx');
var MessageWrapper = require('./message_wrapper.jsx');
+
+var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
+var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
function getCountsStateFromStores() {
-
var count = 0;
var channels = ChannelStore.getAll();
var members = ChannelStore.getAllMembers();
@@ -34,7 +34,7 @@ function getCountsStateFromStores() {
}
});
- return { count: count }
+ return { count: count };
}
var NotifyCounts = React.createClass({
@@ -54,99 +54,11 @@ var NotifyCounts = React.createClass({
return getCountsStateFromStores();
},
render: function() {
- if (this.state.count == 0) {
- return (<span></span>);
- }
- else {
- return (<span className="badge badge-notify">{ this.state.count }</span>);
- }
- }
-});
-
-var NavbarLoginForm = React.createClass({
- handleSubmit: function(e) {
- e.preventDefault();
- var state = { }
-
- var domain = this.refs.domain.getDOMNode().value.trim();
- if (!domain) {
- state.server_error = "A domain is required"
- this.setState(state);
- return;
- }
-
- var email = this.refs.email.getDOMNode().value.trim();
- if (!email) {
- state.server_error = "An email is required"
- this.setState(state);
- return;
- }
-
- var password = this.refs.password.getDOMNode().value.trim();
- if (!password) {
- state.server_error = "A password is required"
- this.setState(state);
- return;
- }
-
- state.server_error = "";
- this.setState(state);
-
- client.loginByEmail(domain, email, password,
- function(data) {
- UserStore.setLastDomain(domain);
- UserStore.setLastEmail(email);
- UserStore.setCurrentUser(data);
-
- var redirect = utils.getUrlParameter("redirect");
- if (redirect) {
- window.location.href = decodeURI(redirect);
- } else {
- window.location.href = '/channels/town-square';
- }
-
- }.bind(this),
- function(err) {
- if (err.message == "Login failed because email address has not been verified") {
- window.location.href = '/verify?domain=' + encodeURIComponent(domain) + '&email=' + encodeURIComponent(email);
- return;
- }
- state.server_error = err.message;
- this.valid = false;
- this.setState(state);
- }.bind(this)
- );
- },
- getInitialState: function() {
- return { };
- },
- render: function() {
- var server_error = this.state.server_error ? <label className="control-label">{this.state.server_error}</label> : null;
-
- var subDomain = utils.getSubDomain();
- var subDomainClass = "form-control hidden";
-
- if (subDomain == "") {
- subDomain = UserStore.getLastDomain();
- subDomainClass = "form-control";
+ if (this.state.count) {
+ return <span className="badge badge-notify">{ this.state.count }</span>;
+ } else {
+ return null;
}
-
- return (
- <form className="navbar-form navbar-right" onSubmit={this.handleSubmit}>
- <a href="/find_team">Find your team</a>
- <div className={server_error ? 'form-group has-error' : 'form-group'}>
- { server_error }
- <input type="text" className={subDomainClass} name="domain" defaultValue={subDomain} ref="domain" placeholder="Domain" />
- </div>
- <div className={server_error ? 'form-group has-error' : 'form-group'}>
- <input type="text" className="form-control" name="email" defaultValue={UserStore.getLastEmail()} ref="email" placeholder="Email" />
- </div>
- <div className={server_error ? 'form-group has-error' : 'form-group'}>
- <input type="password" className="form-control" name="password" ref="password" placeholder="Password" />
- </div>
- <button type="submit" className="btn btn-default">Login</button>
- </form>
- );
}
});
@@ -159,13 +71,14 @@ function getStateFromStores() {
}
module.exports = React.createClass({
+ displayName: 'Navbar',
+
componentDidMount: function() {
ChannelStore.addChangeListener(this._onChange);
ChannelStore.addExtraInfoChangeListener(this._onChange);
- var self = this;
- $('.inner__wrap').click(self.hideSidebars);
+ $('.inner__wrap').click(this.hideSidebars);
- $('body').on('click.infopopover', function(e){
+ $('body').on('click.infopopover', function(e) {
if ($(e.target).attr('data-toggle') !== 'popover'
&& $(e.target).parents('.popover.in').length === 0) {
$('.info-popover').popover('hide');
@@ -181,13 +94,13 @@ module.exports = React.createClass({
},
handleLeave: function(e) {
client.leaveChannel(this.state.channel.id,
- function(data) {
+ function(data, text, req) {
AsyncClient.getChannels(true);
- window.location.href = '/channels/town-square';
+ window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/town-square';
}.bind(this),
function(err) {
AsyncClient.dispatchError(err, "handleLeave");
- }.bind(this)
+ }
);
},
hideSidebars: function(e) {
@@ -204,7 +117,7 @@ module.exports = React.createClass({
});
if (e.target.className != 'navbar-toggle' && e.target.className != 'icon-bar') {
- $('.inner__wrap').removeClass('move--right').removeClass('move--left').removeClass('move--left-small');
+ $('.inner__wrap').removeClass('move--right move--left move--left-small');
$('.sidebar--left').removeClass('move--right');
$('.sidebar--right').removeClass('move--left');
$('.sidebar--menu').removeClass('move--left');
@@ -229,24 +142,22 @@ module.exports = React.createClass({
render: function() {
var currentId = UserStore.getCurrentId();
- var channelName = "";
var popoverContent = "";
- var channelTitle = this.props.teamName;
+ var channelTitle = this.props.teamDisplayName;
var isAdmin = false;
var isDirect = false;
var description = ""
+ var channel = this.state.channel;
- if (this.state.channel) {
- var channel = this.state.channel;
- description = utils.textToJsx(this.state.channel.description, {"singleline": true, "noMentionHighlight": true});
- popoverContent = React.renderToString(<MessageWrapper message={this.state.channel.description}/>);
- channelName = this.state.channel.name;
+ if (channel) {
+ description = utils.textToJsx(channel.description, {"singleline": true, "noMentionHighlight": true});
+ popoverContent = React.renderToString(<MessageWrapper message={channel.description}/>);
isAdmin = this.state.member.roles.indexOf("admin") > -1;
if (channel.type === 'O') {
- channelTitle = this.state.channel.display_name;
+ channelTitle = channel.display_name;
} else if (channel.type === 'P') {
- channelTitle = this.state.channel.display_name;
+ channelTitle = channel.display_name;
} else if (channel.type === 'D') {
isDirect = true;
if (this.state.users.length > 1) {
@@ -258,12 +169,11 @@ module.exports = React.createClass({
}
}
- if(this.state.channel.description.length == 0){
- popoverContent = React.renderToString(<div>No channel description yet. <br /><a href='#' data-toggle='modal' data-desc={this.state.channel.description} data-title={this.state.channel.display_name} data-channelid={this.state.channel.id} data-target='#edit_channel'>Click here</a> to add one.</div>);
+ if (channel.description.length == 0) {
+ popoverContent = React.renderToString(<div>No channel description yet. <br /><a href='#' data-toggle='modal' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id} data-target='#edit_channel'>Click here</a> to add one.</div>);
}
}
- var loginForm = currentId == null ? <NavbarLoginForm /> : null;
var navbar_collapse_button = currentId != null ? null :
<button type="button" className="navbar-toggle" data-toggle="collapse" data-target="#navbar-collapse-1">
<span className="sr-only">Toggle sidebar</span>
@@ -292,60 +202,55 @@ module.exports = React.createClass({
{ navbar_collapse_button }
{ sidebar_collapse_button }
{ right_sidebar_collapse_button }
- { !isDirect && this.state.channel ?
- <div className="navbar-brand">
- <div className="dropdown">
- <div data-toggle="popover" data-content={popoverContent} className="description info-popover"></div>
- <a href="#" className="dropdown-toggle theme" type="button" id="channel_header_dropdown" data-toggle="dropdown" aria-expanded="true">
- <strong className="heading">{channelTitle} </strong>
- <span className="glyphicon glyphicon-chevron-down header-dropdown__icon"></span>
- </a>
- <ul className="dropdown-menu" role="menu" aria-labelledby="channel_header_dropdown">
- <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_invite" href="#">Add Members</a></li>
- { isAdmin ?
- <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_members" href="#">Manage Members</a></li>
- : ""
- }
- <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#edit_channel" data-desc={this.state.channel.description} data-title={this.state.channel.display_name} data-channelid={this.state.channel.id}>Set Channel Description...</a></li>
- <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#channel_notifications" data-title={this.state.channel.display_name} data-channelid={this.state.channel.id}>Notification Preferences</a></li>
- { isAdmin && channelName != Constants.DEFAULT_CHANNEL ?
- <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#rename_channel" data-display={this.state.channel.display_name} data-name={this.state.channel.name} data-channelid={this.state.channel.id}>Rename Channel...</a></li>
- : ""
- }
- { isAdmin && channelName != Constants.DEFAULT_CHANNEL ?
- <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#delete_channel" data-title={this.state.channel.display_name} data-channelid={this.state.channel.id}>Delete Channel...</a></li>
- : ""
- }
- { channelName != Constants.DEFAULT_CHANNEL ?
- <li role="presentation"><a role="menuitem" href="#" onClick={this.handleLeave}>Leave Channel</a></li>
- : ""
- }
- </ul>
+ { !isDirect && channel ?
+ <div className="navbar-brand">
+ <div className="dropdown">
+ <div data-toggle="popover" data-content={popoverContent} className="description info-popover"></div>
+ <a href="#" className="dropdown-toggle theme" type="button" id="channel_header_dropdown" data-toggle="dropdown" aria-expanded="true">
+ <span className="heading">{channelTitle} </span>
+ <span className="glyphicon glyphicon-chevron-down header-dropdown__icon"></span>
+ </a>
+ <ul className="dropdown-menu" role="menu" aria-labelledby="channel_header_dropdown">
+ { !ChannelStore.isDefault(channel) ?
+ <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_invite" href="#">Add Members</a></li>
+ : null
+ }
+ { isAdmin && !ChannelStore.isDefault(channel) ?
+ <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_members" href="#">Manage Members</a></li>
+ : null
+ }
+ <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#edit_channel" data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set Channel Description...</a></li>
+ <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#channel_notifications" data-title={channel.display_name} data-channelid={channel.id}>Notification Preferences</a></li>
+ { isAdmin && !ChannelStore.isDefault(channel) ?
+ <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#rename_channel" data-display={channel.display_name} data-name={channel.name} data-channelid={channel.id}>Rename Channel...</a></li>
+ : null
+ }
+ { isAdmin && !ChannelStore.isDefault(channel) ?
+ <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#delete_channel" data-title={channel.display_name} data-channelid={channel.id}>Delete Channel...</a></li>
+ : null
+ }
+ { !ChannelStore.isDefault(channel) ?
+ <li role="presentation"><a role="menuitem" href="#" onClick={this.handleLeave}>Leave Channel</a></li>
+ : null
+ }
+ </ul>
+ </div>
</div>
- </div>
- : "" }
- { isDirect && this.state.channel ?
+ : null
+ }
+ { isDirect && channel ?
<div className="navbar-brand">
- <strong>
- <a href="#"><strong className="heading">{channelTitle}</strong></a>
- </strong>
+ <a href="#" className="heading">{ channelTitle }</a>
</div>
- : "" }
- { !this.state.channel ?
+ : null }
+ { !channel ?
<div className="navbar-brand">
- <strong>
- <a href="/"><strong className="heading">{ channelTitle }</strong></a>
- </strong>
+ <a href="/" className="heading">{ channelTitle }</a>
</div>
: "" }
</div>
- <div className="collapse navbar-collapse" id="navbar-collapse-1">
- { loginForm }
- </div>
</div>
</nav>
);
-}
+ }
});
-
-
diff --git a/web/react/components/new_channel.jsx b/web/react/components/new_channel.jsx
index 160241c1c..49e088458 100644
--- a/web/react/components/new_channel.jsx
+++ b/web/react/components/new_channel.jsx
@@ -6,6 +6,8 @@ var utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx');
var asyncClient = require('../utils/async_client.jsx');
var UserStore = require('../stores/user_store.jsx');
+var TeamStore = require('../stores/team_store.jsx');
+var Constants = require('../utils/constants.jsx');
module.exports = React.createClass({
handleSubmit: function(e) {
@@ -60,13 +62,13 @@ module.exports = React.createClass({
var self = this;
client.createChannel(channel,
- function(data) {
+ function() {
this.refs.display_name.getDOMNode().value = "";
this.refs.channel_name.getDOMNode().value = "";
this.refs.channel_desc.getDOMNode().value = "";
$(self.refs.modal.getDOMNode()).modal('hide');
- window.location.href = "/channels/" + channel.name;
+ window.location = TeamStore.getCurrentTeamUrl() + "/channels/" + channel.name;
asyncClient.getChannels(true);
}.bind(this),
function(err) {
@@ -122,7 +124,7 @@ module.exports = React.createClass({
</div>
<div className="form-group">
<label className='control-label'>Description</label>
- <textarea className="form-control" style={{resize: "none"}} ref="channel_desc" rows="3" placeholder="Description" maxLength="1024"></textarea>
+ <textarea className="form-control no-resize" ref="channel_desc" rows="3" placeholder="Description" maxLength="1024"></textarea>
</div>
{ server_error }
</form>
diff --git a/web/react/components/password_reset.jsx b/web/react/components/password_reset.jsx
index 24566c7b1..b2edea620 100644
--- a/web/react/components/password_reset.jsx
+++ b/web/react/components/password_reset.jsx
@@ -10,13 +10,6 @@ SendResetPasswordLink = React.createClass({
e.preventDefault();
var state = {};
- var domain = this.refs.domain.getDOMNode().value.trim();
- if (!domain) {
- state.error = "A domain is required"
- this.setState(state);
- return;
- }
-
var email = this.refs.email.getDOMNode().value.trim();
if (!email) {
state.error = "Please enter a valid email address."
@@ -29,17 +22,17 @@ SendResetPasswordLink = React.createClass({
data = {};
data['email'] = email;
- data['domain'] = domain;
+ data['name'] = this.props.teamName;
client.sendPasswordReset(data,
- function(data) {
- this.setState({ error: null, update_text: <p>A password reset link has been sent to <b>{email}</b> for your <b>{this.props.teamName}</b> team on {config.SiteName}.com.</p>, more_update_text: "Please check your inbox." });
- $(this.refs.reset_form.getDOMNode()).hide();
- }.bind(this),
- function(err) {
- this.setState({ error: err.message, update_text: null, more_update_text: null });
- }.bind(this)
- );
+ function(data) {
+ this.setState({ error: null, update_text: <p>A password reset link has been sent to <b>{email}</b> for your <b>{this.props.teamDisplayName}</b> team on {window.location.hostname}.</p>, more_update_text: "Please check your inbox." });
+ $(this.refs.reset_form.getDOMNode()).hide();
+ }.bind(this),
+ function(err) {
+ this.setState({ error: err.message, update_text: null, more_update_text: null });
+ }.bind(this)
+ );
},
getInitialState: function() {
return {};
@@ -48,24 +41,13 @@ SendResetPasswordLink = React.createClass({
var update_text = this.state.update_text ? <div className="reset-form alert alert-success">{this.state.update_text}{this.state.more_update_text}</div> : null;
var error = this.state.error ? <div className="form-group has-error"><label className="control-label">{this.state.error}</label></div> : null;
- var subDomain = utils.getSubDomain();
- var subDomainClass = "form-control hidden";
-
- if (subDomain == "") {
- subDomain = UserStore.getLastDomain();
- subDomainClass = "form-control";
- }
-
return (
<div className="col-sm-12">
<div className="signup-team__container">
<h3>Password Reset</h3>
{ update_text }
<form onSubmit={this.handleSendLink} ref="reset_form">
- <p>{"To reset your password, enter the email address you used to sign up for " + this.props.teamName + "."}</p>
- <div className="form-group">
- <input type="text" className={subDomainClass} name="domain" defaultValue={subDomain} ref="domain" placeholder="Domain" />
- </div>
+ <p>{"To reset your password, enter the email address you used to sign up for " + this.props.teamDisplayName + "."}</p>
<div className={error ? 'form-group has-error' : 'form-group'}>
<input type="text" className="form-control" name="email" ref="email" placeholder="Email" />
</div>
@@ -83,13 +65,6 @@ ResetPassword = React.createClass({
e.preventDefault();
var state = {};
- var domain = this.refs.domain.getDOMNode().value.trim();
- if (!domain) {
- state.error = "A domain is required"
- this.setState(state);
- return;
- }
-
var password = this.refs.password.getDOMNode().value.trim();
if (!password || password.length < 5) {
state.error = "Please enter at least 5 characters."
@@ -104,41 +79,30 @@ ResetPassword = React.createClass({
data['new_password'] = password;
data['hash'] = this.props.hash;
data['data'] = this.props.data;
- data['domain'] = domain;
+ data['name'] = this.props.teamName;
client.resetPassword(data,
- function(data) {
- this.setState({ error: null, update_text: "Your password has been updated successfully." });
- }.bind(this),
- function(err) {
- this.setState({ error: err.message, update_text: null });
- }.bind(this)
- );
+ function(data) {
+ this.setState({ error: null, update_text: "Your password has been updated successfully." });
+ }.bind(this),
+ function(err) {
+ this.setState({ error: err.message, update_text: null });
+ }.bind(this)
+ );
},
getInitialState: function() {
return {};
},
render: function() {
- var update_text = this.state.update_text ? <div className="form-group"><br/><label className="control-label reset-form">{this.state.update_text} Click <a href="/login">here</a> to log in.</label></div> : null;
+ var update_text = this.state.update_text ? <div className="form-group"><br/><label className="control-label reset-form">{this.state.update_text} Click <a href={"/" + this.props.teamName + "/login"}>here</a> to log in.</label></div> : null;
var error = this.state.error ? <div className="form-group has-error"><label className="control-label">{this.state.error}</label></div> : null;
- var subDomain = this.props.domain != "" ? this.props.domain : utils.getSubDomain();
- var subDomainClass = "form-control hidden";
-
- if (subDomain == "") {
- subDomain = UserStore.getLastDomain();
- subDomainClass = "form-control";
- }
-
return (
<div className="col-sm-12">
<div className="signup-team__container">
<h3>Password Reset</h3>
<form onSubmit={this.handlePasswordReset}>
- <p>{"Enter a new password for your " + this.props.teamName + " " + config.SiteName + " account."}</p>
- <div className="form-group">
- <input type="text" className={subDomainClass} name="domain" defaultValue={subDomain} ref="domain" placeholder="Domain" />
- </div>
+ <p>{"Enter a new password for your " + this.props.teamDisplayName + " " + config.SiteName + " account."}</p>
<div className={error ? 'form-group has-error' : 'form-group'}>
<input type="password" className="form-control" name="password" ref="password" placeholder="Password" />
</div>
@@ -161,14 +125,15 @@ module.exports = React.createClass({
if (this.props.isReset === "false") {
return (
<SendResetPasswordLink
+ teamDisplayName={this.props.teamDisplayName}
teamName={this.props.teamName}
/>
);
} else {
return (
<ResetPassword
+ teamDisplayName={this.props.teamDisplayName}
teamName={this.props.teamName}
- domain={this.props.domain}
hash={this.props.hash}
data={this.props.data}
/>
diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx
index 04b5ba082..e72a2d001 100644
--- a/web/react/components/post.jsx
+++ b/web/react/components/post.jsx
@@ -10,6 +10,7 @@ var UserStore = require('../stores/user_store.jsx');
var ActionTypes = Constants.ActionTypes;
module.exports = React.createClass({
+ displayName: "Post",
componentDidMount: function() {
$('.modal').on('show.bs.modal', function () {
$('.modal-body').css('overflow-y', 'auto');
@@ -19,7 +20,7 @@ module.exports = React.createClass({
handleCommentClick: function(e) {
e.preventDefault();
- data = {};
+ var data = {};
data.order = [this.props.post.id];
data.posts = this.props.posts;
@@ -33,6 +34,10 @@ module.exports = React.createClass({
results: null
});
},
+ forceUpdateInfo: function() {
+ this.refs.info.forceUpdate();
+ this.refs.header.forceUpdate();
+ },
getInitialState: function() {
return { };
},
@@ -48,7 +53,6 @@ module.exports = React.createClass({
var commentCount = 0;
var commentRootId = parentPost ? post.root_id : post.id;
- var rootUser = "";
for (var postId in posts) {
if (posts[postId].root_id == commentRootId) {
commentCount += 1;
@@ -57,12 +61,7 @@ module.exports = React.createClass({
var error = this.state.error ? <div className='form-group has-error'><label className='control-label'>{ this.state.error }</label></div> : null;
- if (this.props.sameRoot){
- rootUser = "same--root";
- }
- else {
- rootUser = "other--root";
- }
+ var rootUser = this.props.sameRoot ? "same--root" : "other--root";
var postType = "";
if (type != "Post"){
@@ -74,18 +73,20 @@ module.exports = React.createClass({
currentUserCss = "current--user";
}
+ var timestamp = UserStore.getCurrentUser().update_at;
+
return (
<div>
<div id={post.id} className={"post " + this.props.sameUser + " " + rootUser + " " + postType + " " + currentUserCss}>
{ !this.props.hideProfilePic ?
<div className="post-profile-img__container">
- <img className="post-profile-img" src={"/api/v1/users/" + post.user_id + "/image"} height="36" width="36" />
+ <img className="post-profile-img" src={"/api/v1/users/" + post.user_id + "/image?time=" + timestamp} height="36" width="36" />
</div>
- : "" }
+ : null }
<div className="post__content">
- <PostHeader post={post} sameRoot={this.props.sameRoot} commentCount={commentCount} handleCommentClick={this.handleCommentClick} isLastComment={this.props.isLastComment} />
+ <PostHeader ref="header" post={post} sameRoot={this.props.sameRoot} commentCount={commentCount} handleCommentClick={this.handleCommentClick} isLastComment={this.props.isLastComment} />
<PostBody post={post} sameRoot={this.props.sameRoot} parentPost={parentPost} posts={posts} handleCommentClick={this.handleCommentClick} />
- <PostInfo post={post} sameRoot={this.props.sameRoot} commentCount={commentCount} handleCommentClick={this.handleCommentClick} allowReply="true" />
+ <PostInfo ref="info" post={post} sameRoot={this.props.sameRoot} commentCount={commentCount} handleCommentClick={this.handleCommentClick} allowReply="true" />
</div>
</div>
</div>
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index 7d5ef4d33..d9678df30 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -71,11 +71,22 @@ module.exports = React.createClass({
name = <a className="theme" onClick={function(){ utils.searchForTerm(profile.username); }}>{profile.username}</a>;
}
- var message = parentPost.message;
+ var message = ""
+ if(parentPost.message) {
+ message = utils.replaceHtmlEntities(parentPost.message)
+ } else if (parentPost.filenames.length) {
+ message = parentPost.filenames[0].split('/').pop();
+
+ if (parentPost.filenames.length === 2) {
+ message += " plus 1 other file";
+ } else if (parentPost.filenames.length > 2) {
+ message += " plus " + (parentPost.filenames.length - 1) + " other files";
+ }
+ }
comment = (
<p className="post-link">
- <span>Commented on {name}{apostrophe} message: <a className="theme" onClick={this.props.handleCommentClick}>{utils.replaceHtmlEntities(message)}</a></span>
+ <span>Commented on {name}{apostrophe} message: <a className="theme" onClick={this.props.handleCommentClick}>{message}</a></span>
</p>
);
@@ -120,7 +131,7 @@ module.exports = React.createClass({
return (
<div className="post-body">
{ comment }
- <p key={post.Id+"_message"} className={postClass}><span>{inner}</span></p>
+ <p key={post.id+"_message"} className={postClass}><span>{inner}</span></p>
{ filenames && filenames.length > 0 ?
<div className="post-image__columns">
{ postFiles }
diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx
index cf01747f0..d6422fe3a 100644
--- a/web/react/components/post_info.jsx
+++ b/web/react/components/post_info.jsx
@@ -11,6 +11,7 @@ module.exports = React.createClass({
render: function() {
var post = this.props.post;
var isOwner = UserStore.getCurrentId() == post.user_id;
+ var isAdmin = UserStore.getCurrentUser().roles.indexOf("admin") > -1
var type = "Post"
if (post.root_id.length > 0) {
@@ -30,13 +31,11 @@ module.exports = React.createClass({
<div className="dropdown">
{ isOwner || (this.props.allowReply === "true" && type != "Comment") ?
<div>
- <a href="#" className="dropdown-toggle theme" type="button" data-toggle="dropdown" aria-expanded="false">
- [...]
- </a>
+ <a href="#" className="dropdown-toggle theme" type="button" data-toggle="dropdown" aria-expanded="false" />
<ul className="dropdown-menu" role="menu">
{ isOwner ? <li role="presentation"><a href="#" role="menuitem" data-toggle="modal" data-target="#edit_post" data-title={type} data-message={post.message} data-postid={post.id} data-channelid={post.channel_id} data-comments={type === "Post" ? this.props.commentCount : 0}>Edit</a></li>
: "" }
- { isOwner ? <li role="presentation"><a href="#" role="menuitem" data-toggle="modal" data-target="#delete_post" data-title={type} data-postid={post.id} data-channelid={post.channel_id} data-comments={type === "Post" ? this.props.commentCount : 0}>Delete</a></li>
+ { isOwner || isAdmin ? <li role="presentation"><a href="#" role="menuitem" data-toggle="modal" data-target="#delete_post" data-title={type} data-postid={post.id} data-channelid={post.channel_id} data-comments={type === "Post" ? this.props.commentCount : 0}>Delete</a></li>
: "" }
{ this.props.allowReply === "true" ? <li role="presentation"><a className="reply-link theme" href="#" onClick={this.props.handleCommentClick}>Reply</a></li>
: "" }
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index d6dc9ce30..c058455ba 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -6,8 +6,8 @@ var ChannelStore = require('../stores/channel_store.jsx');
var UserStore = require('../stores/user_store.jsx');
var UserProfile = require( './user_profile.jsx' );
var AsyncClient = require('../utils/async_client.jsx');
-var CreatePost = require('./create_post.jsx');
var Post = require('./post.jsx');
+var LoadingScreen = require('./loading_screen.jsx');
var SocketStore = require('../stores/socket_store.jsx');
var utils = require('../utils/utils.jsx');
var Client = require('../utils/client.jsx');
@@ -26,37 +26,8 @@ function getStateFromStores() {
};
}
-function changeColor(col, amt) {
-
- var usePound = false;
-
- if (col[0] == "#") {
- col = col.slice(1);
- usePound = true;
- }
-
- var num = parseInt(col,16);
-
- var r = (num >> 16) + amt;
-
- if (r > 255) r = 255;
- else if (r < 0) r = 0;
-
- var b = ((num >> 8) & 0x00FF) + amt;
-
- if (b > 255) b = 255;
- else if (b < 0) b = 0;
-
- var g = (num & 0x0000FF) + amt;
-
- if (g > 255) g = 255;
- else if (g < 0) g = 0;
-
- return (usePound?"#":"") + String("000000" + (g | (b << 8) | (r << 16)).toString(16)).slice(-6);
-
-}
-
module.exports = React.createClass({
+ displayName: "PostList",
scrollPosition: 0,
preventScrollTrigger: false,
gotMorePosts: false,
@@ -69,7 +40,7 @@ module.exports = React.createClass({
utils.changeCss('a.theme', 'color:'+user.props.theme+'; fill:'+user.props.theme+'!important;');
utils.changeCss('div.theme', 'background-color:'+user.props.theme+';');
utils.changeCss('.btn.btn-primary', 'background: ' + user.props.theme+';');
- utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + changeColor(user.props.theme, -10) +';');
+ utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, -10) +';');
utils.changeCss('.modal .modal-header', 'background: ' + user.props.theme+';');
utils.changeCss('.mention', 'background: ' + user.props.theme+';');
utils.changeCss('.mention-link', 'color: ' + user.props.theme+';');
@@ -78,6 +49,7 @@ module.exports = React.createClass({
PostStore.addChangeListener(this._onChange);
ChannelStore.addChangeListener(this._onChange);
+ UserStore.addStatusesChangeListener(this._onTimeChange);
SocketStore.addChangeListener(this._onSocketChange);
$(".post-list-holder-by-time").perfectScrollbar();
@@ -157,28 +129,25 @@ module.exports = React.createClass({
componentWillUnmount: function() {
PostStore.removeChangeListener(this._onChange);
ChannelStore.removeChangeListener(this._onChange);
+ UserStore.removeStatusesChangeListener(this._onTimeChange);
SocketStore.removeChangeListener(this._onSocketChange);
$('body').off('click.userpopover');
},
resize: function() {
+ var post_holder = $(".post-list-holder-by-time")[0];
+ this.preventScrollTrigger = true;
if (this.gotMorePosts) {
this.gotMorePosts = false;
- var post_holder = $(".post-list-holder-by-time")[0];
- this.preventScrollTrigger = true;
$(post_holder).scrollTop($(post_holder).scrollTop() + (post_holder.scrollHeight-this.oldScrollHeight) );
- $(post_holder).perfectScrollbar('update');
} else {
- var post_holder = $(".post-list-holder-by-time")[0];
- this.preventScrollTrigger = true;
if ($("#new_message")[0] && !this.scrolledToNew) {
$(post_holder).scrollTop($(post_holder).scrollTop() + $("#new_message").offset().top - 63);
- $(post_holder).perfectScrollbar('update');
this.scrolledToNew = true;
} else {
$(post_holder).scrollTop(post_holder.scrollHeight);
- $(post_holder).perfectScrollbar('update');
}
}
+ $(post_holder).perfectScrollbar('update');
},
_onChange: function() {
var newState = getStateFromStores();
@@ -239,12 +208,8 @@ module.exports = React.createClass({
var index = post_list.order.indexOf(msg.props.post_id);
if (index > -1) post_list.order.splice(index, 1);
- var scrollSave = $(".post-list-holder-by-time").scrollTop();
-
this.setState({ post_list: post_list });
- $(".post-list-holder-by-time").scrollTop(scrollSave)
-
PostStore.storePosts(msg.channel_id, post_list);
} else {
AsyncClient.getPosts(true, msg.channel_id);
@@ -253,10 +218,16 @@ module.exports = React.createClass({
if (activeRootPostId === msg.props.post_id && UserStore.getCurrentId() != msg.user_id) {
$('#post_deleted').modal('show');
}
- } else if(msg.action == "new_user") {
+ } else if (msg.action == "new_user") {
AsyncClient.getProfiles();
}
},
+ _onTimeChange: function() {
+ for (var id in this.state.post_list.posts) {
+ if (!this.refs[id]) continue;
+ this.refs[id].forceUpdateInfo();
+ }
+ },
getMorePosts: function(e) {
e.preventDefault();
@@ -297,7 +268,7 @@ module.exports = React.createClass({
},
function(err) {
$(self.refs.loadmore.getDOMNode()).text("Load more messages");
- dispatchError(err, "getPosts");
+ AsyncClient.dispatchError(err, "getPosts");
}
);
},
@@ -338,16 +309,19 @@ module.exports = React.createClass({
var teammate = utils.getDirectTeammate(channel.id)
if (teammate) {
- var teammate_name = teammate.full_name.length > 0 ? teammate.full_name : teammate.username;
+ var teammate_name = teammate.nickname.length > 0 ? teammate.nickname : teammate.username;
more_messages = (
<div className="channel-intro">
<div className="post-profile-img__container channel-intro-img">
- <img className="post-profile-img" src={"/api/v1/users/" + teammate.id + "/image"} height="50" width="50" />
+ <img className="post-profile-img" src={"/api/v1/users/" + teammate.id + "/image?time=" + teammate.update_at} height="50" width="50" />
</div>
<div className="channel-intro-profile">
<strong><UserProfile userId={teammate.id} /></strong>
</div>
- <p className="channel-intro-text">{"This is the start of your private message history with " + teammate_name + "." }<br/>{"Private messages and files shared here are not shown to people outside this area."}</p>
+ <p className="channel-intro-text">
+ {"This is the start of your private message history with " + teammate_name + "." }<br/>
+ {"Private messages and files shared here are not shown to people outside this area."}
+ </p>
</div>
);
} else {
@@ -370,7 +344,7 @@ module.exports = React.createClass({
}
}
- if (channel.name === Constants.DEFAULT_CHANNEL) {
+ if (ChannelStore.isDefault(channel)) {
more_messages = (
<div className="channel-intro">
<h4 className="channel-intro-title">Welcome</h4>
@@ -410,7 +384,7 @@ module.exports = React.createClass({
{ channel.type === 'P' ? " Only invited members can see this private group." : " Any member can join and read this channel." }
<br/>
<a className="intro-links" href="#" style={userStyle} data-toggle="modal" data-target="#edit_channel" data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}><i className="fa fa-pencil"></i>Set a description</a>
- <a className="intro-links" style={userStyle} data-toggle="modal" data-target="#channel_invite"><i className="fa fa-user-plus"></i>Invite others to this {ui_type}</a>
+ <a className="intro-links" href="#" style={userStyle} data-toggle="modal" data-target="#channel_invite"><i className="fa fa-user-plus"></i>Invite others to this {ui_type}</a>
</p>
</div>
);
@@ -420,37 +394,35 @@ module.exports = React.createClass({
var postCtls = [];
- if (posts != undefined) {
+ if (posts) {
var previousPostDay = posts[order[order.length-1]] ? utils.getDateForUnixTicks(posts[order[order.length-1]].create_at): new Date();
- var currentPostDay = new Date();
+ var currentPostDay;
for (var i = order.length-1; i >= 0; i--) {
var post = posts[order[i]];
- var parentPost;
+ var parentPost = post.parent_id ? posts[post.parent_id] : null;
- if (post.parent_id) {
- parentPost = posts[post.parent_id];
- } else {
- parentPost = null;
- }
+ var sameUser = '';
+ var sameRoot = false;
+ var hideProfilePic = false;
+ var prevPost = (i < order.length - 1) ? posts[order[i + 1]] : null;
- var sameUser = i < order.length-1 && posts[order[i+1]].user_id === post.user_id && post.create_at - posts[order[i+1]].create_at <= 1000*60*5 ? "same--user" : "";
- var sameRoot = i < order.length-1 && post.root_id != "" && (posts[order[i+1]].id === post.root_id || posts[order[i+1]].root_id === post.root_id) ? true : false;
+ if (prevPost) {
+ sameUser = (prevPost.user_id === post.user_id) && (post.create_at - prevPost.create_at <= 1000*60*5) ? "same--user" : "";
+ sameRoot = utils.isComment(post) && (prevPost.id === post.root_id || prevPost.root_id === post.root_id);
- // we only hide the profile pic if the previous post is not a comment, the current post is not a comment, and the previous post was made by the same user as the current post
- var hideProfilePic = i < order.length-1 && posts[order[i+1]].user_id === post.user_id && posts[order[i+1]].root_id === '' && post.root_id === '';
+ // we only hide the profile pic if the previous post is not a comment, the current post is not a comment, and the previous post was made by the same user as the current post
+ hideProfilePic = (prevPost.user_id === post.user_id) && !utils.isComment(prevPost) && !utils.isComment(post);
+ }
// check if it's the last comment in a consecutive string of comments on the same post
- var isLastComment = false;
- if (utils.isComment(post)) {
- // it is the last comment if it is last post in the channel or the next post has a different root post
- isLastComment = (i === 0 || posts[order[i-1]].root_id != post.root_id);
- }
+ // it is the last comment if it is last post in the channel or the next post has a different root post
+ var isLastComment = utils.isComment(post) && (i === 0 || posts[order[i-1]].root_id != post.root_id);
- var postCtl = <Post sameUser={sameUser} sameRoot={sameRoot} post={post} parentPost={parentPost} key={post.id} posts={posts} hideProfilePic={hideProfilePic} isLastComment={isLastComment} />;
+ var postCtl = <Post ref={post.id} sameUser={sameUser} sameRoot={sameRoot} post={post} parentPost={parentPost} key={post.id} posts={posts} hideProfilePic={hideProfilePic} isLastComment={isLastComment} />;
currentPostDay = utils.getDateForUnixTicks(post.create_at);
- if(currentPostDay.getDate() !== previousPostDay.getDate() || currentPostDay.getMonth() !== previousPostDay.getMonth() || currentPostDay.getFullYear() !== previousPostDay.getFullYear()) {
+ if (currentPostDay.toDateString() != previousPostDay.toDateString()) {
postCtls.push(
<div className="date-separator">
<hr className="separator__hr" />
@@ -469,20 +441,10 @@ module.exports = React.createClass({
);
}
postCtls.push(postCtl);
- previousPostDay = utils.getDateForUnixTicks(post.create_at);
+ previousPostDay = currentPostDay;
}
- }
- else {
- postCtls.push(
- <div ref="loadingscreen" className="loading-screen">
- <div className="loading__content">
- <h3>Loading</h3>
- <div id="round_1" className="round"></div>
- <div id="round_2" className="round"></div>
- <div id="round_3" className="round"></div>
- </div>
- </div>
- );
+ } else {
+ postCtls.push(<LoadingScreen position="absolute" />);
}
return (
@@ -497,5 +459,3 @@ module.exports = React.createClass({
);
}
});
-
-
diff --git a/web/react/components/post_right.jsx b/web/react/components/post_right.jsx
index 115ee87d4..581a1abe9 100644
--- a/web/react/components/post_right.jsx
+++ b/web/react/components/post_right.jsx
@@ -43,7 +43,7 @@ RhsHeaderPost = React.createClass({
});
},
render: function() {
- var back = this.props.fromSearch ? <a href="#" onClick={this.handleBack} style={{color:"black"}}>{"< "}</a> : "";
+ var back = this.props.fromSearch ? <a href="#" onClick={this.handleBack} className="sidebar--right__back"><i className="fa fa-chevron-left"></i></a> : "";
return (
<div className="sidebar--right__header">
@@ -67,6 +67,8 @@ RootPost = React.createClass({
var message = utils.textToJsx(this.props.post.message);
var filenames = this.props.post.filenames;
var isOwner = UserStore.getCurrentId() == this.props.post.user_id;
+ var timestamp = UserStore.getProfile(this.props.post.user_id).update_at;
+ var channel = ChannelStore.get(this.props.post.channel_id);
var type = "Post";
if (this.props.post.root_id.length > 0) {
@@ -78,6 +80,10 @@ RootPost = React.createClass({
currentUserCss = "current--user";
}
+ if (channel) {
+ channelName = (channel.type === 'D') ? "Private Message" : channel.display_name;
+ }
+
if (filenames) {
var postFiles = [];
var images = [];
@@ -117,8 +123,9 @@ RootPost = React.createClass({
return (
<div className={"post post--root " + currentUserCss}>
+ <div className="post-right-channel__name">{ channelName }</div>
<div className="post-profile-img__container">
- <img className="post-profile-img" src={"/api/v1/users/" + this.props.post.user_id + "/image"} height="36" width="36" />
+ <img className="post-profile-img" src={"/api/v1/users/" + this.props.post.user_id + "/image?time=" + timestamp} height="36" width="36" />
</div>
<div className="post__content">
<ul className="post-header">
@@ -128,9 +135,7 @@ RootPost = React.createClass({
<div className="dropdown">
{ isOwner ?
<div>
- <a href="#" className="dropdown-toggle theme" type="button" data-toggle="dropdown" aria-expanded="false">
- [...]
- </a>
+ <a href="#" className="dropdown-toggle theme" type="button" data-toggle="dropdown" aria-expanded="false" />
<ul className="dropdown-menu" role="menu">
<li role="presentation"><a href="#" role="menuitem" data-toggle="modal" data-target="#edit_post" data-title={type} data-message={this.props.post.message} data-postid={this.props.post.id} data-channelid={this.props.post.channel_id}>Edit</a></li>
<li role="presentation"><a href="#" role="menuitem" data-toggle="modal" data-target="#delete_post" data-title={type} data-postid={this.props.post.id} data-channelid={this.props.post.channel_id} data-comments={this.props.commentCount}>Delete</a></li>
@@ -227,11 +232,12 @@ CommentPost = React.createClass({
}
var message = utils.textToJsx(this.props.post.message);
+ var timestamp = UserStore.getCurrentUser().update_at;
return (
<div className={commentClass + " " + currentUserCss}>
<div className="post-profile-img__container">
- <img className="post-profile-img" src={"/api/v1/users/" + this.props.post.user_id + "/image"} height="36" width="36" />
+ <img className="post-profile-img" src={"/api/v1/users/" + this.props.post.user_id + "/image?time=" + timestamp} height="36" width="36" />
</div>
<div className="post__content">
<ul className="post-header">
@@ -240,9 +246,7 @@ CommentPost = React.createClass({
<li className="post-header-col post-header__reply">
{ isOwner ?
<div className="dropdown" onClick={function(e){$('.post-list-holder-by-time').scrollTop($(".post-list-holder-by-time").scrollTop() + 50);}}>
- <a href="#" className="dropdown-toggle theme" type="button" data-toggle="dropdown" aria-expanded="false">
- [...]
- </a>
+ <a href="#" className="dropdown-toggle theme" type="button" data-toggle="dropdown" aria-expanded="false" />
<ul className="dropdown-menu" role="menu">
<li role="presentation"><a href="#" role="menuitem" data-toggle="modal" data-target="#edit_post" data-title={type} data-message={this.props.post.message} data-postid={this.props.post.id} data-channelid={this.props.post.channel_id}>Edit</a></li>
<li role="presentation"><a href="#" role="menuitem" data-toggle="modal" data-target="#delete_post" data-title={type} data-postid={this.props.post.id} data-channelid={this.props.post.channel_id} data-comments={0}>Delete</a></li>
@@ -282,6 +286,7 @@ module.exports = React.createClass({
componentDidMount: function() {
PostStore.addSelectedPostChangeListener(this._onChange);
PostStore.addChangeListener(this._onChangeAll);
+ UserStore.addStatusesChangeListener(this._onTimeChange);
this.resize();
var self = this;
$(window).resize(function(){
@@ -294,6 +299,7 @@ module.exports = React.createClass({
componentWillUnmount: function() {
PostStore.removeSelectedPostChangeListener(this._onChange);
PostStore.removeChangeListener(this._onChangeAll);
+ UserStore.removeStatusesChangeListener(this._onTimeChange);
},
_onChange: function() {
if (this.isMounted()) {
@@ -304,7 +310,6 @@ module.exports = React.createClass({
}
},
_onChangeAll: function() {
-
if (this.isMounted()) {
// if something was changed in the channel like adding a
@@ -333,6 +338,12 @@ module.exports = React.createClass({
this.setState(getStateFromStores());
}
},
+ _onTimeChange: function() {
+ for (var id in this.state.post_list.posts) {
+ if (!this.refs[id]) continue;
+ this.refs[id].forceUpdate();
+ }
+ },
getInitialState: function() {
return getStateFromStores();
},
@@ -392,7 +403,7 @@ module.exports = React.createClass({
<RootPost post={root_post} commentCount={posts_array.length}/>
<div className="post-right-comments-container">
{ posts_array.map(function(cpost) {
- return <CommentPost key={cpost.id} post={cpost} selected={ (cpost.id == selected_post.id) } />
+ return <CommentPost ref={cpost.id} key={cpost.id} post={cpost} selected={ (cpost.id == selected_post.id) } />
})}
</div>
<div className="post-create__container">
diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx
index b4ccb2937..2ae331626 100644
--- a/web/react/components/rename_channel_modal.jsx
+++ b/web/react/components/rename_channel_modal.jsx
@@ -6,6 +6,8 @@ var utils = require('../utils/utils.jsx');
var Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
+var TeamStore = require('../stores/team_store.jsx');
+var Constants = require('../utils/constants.jsx');
module.exports = React.createClass({
handleSubmit: function(e) {
@@ -60,12 +62,12 @@ module.exports = React.createClass({
return;
Client.updateChannel(channel,
- function(data) {
+ function(data, text, req) {
this.refs.display_name.getDOMNode().value = "";
this.refs.channel_name.getDOMNode().value = "";
$('#' + this.props.modalId).modal('hide');
- window.location.href = '/channels/' + this.state.channel_name;
+ window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + this.state.channel_name;
AsyncClient.getChannels(true);
}.bind(this),
function(err) {
diff --git a/web/react/components/rename_team_modal.jsx b/web/react/components/rename_team_modal.jsx
index 67a150b9d..a6da57b67 100644
--- a/web/react/components/rename_team_modal.jsx
+++ b/web/react/components/rename_team_modal.jsx
@@ -24,13 +24,13 @@ module.exports = React.createClass({
if (!valid)
return;
- if (this.props.teamName === name)
+ if (this.props.teamDisplayName === name)
return;
var data = {};
data["new_name"] = name;
- Client.updateTeamName(data,
+ Client.updateTeamDisplayName(data,
function(data) {
$('#rename_team_link').modal('hide');
window.location.reload();
@@ -47,11 +47,11 @@ module.exports = React.createClass({
componentDidMount: function() {
var self = this;
$(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) {
- self.setState({ name: self.props.teamName });
+ self.setState({ name: self.props.teamDisplayName });
});
},
getInitialState: function() {
- return { name: this.props.teamName };
+ return { name: this.props.teamDisplayName };
},
render: function() {
diff --git a/web/react/components/search_bar.jsx b/web/react/components/search_bar.jsx
index cddb738f9..f21f0cd58 100644
--- a/web/react/components/search_bar.jsx
+++ b/web/react/components/search_bar.jsx
@@ -3,6 +3,7 @@
var client = require('../utils/client.jsx');
+var AsyncClient = require('../utils/async_client.jsx');
var PostStore = require('../stores/post_store.jsx');
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var utils = require('../utils/utils.jsx');
@@ -10,14 +11,14 @@ var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
function getSearchTermStateFromStores() {
- term = PostStore.getSearchTerm();
- if (!term) term = "";
+ var term = PostStore.getSearchTerm() || '';
return {
search_term: term
};
}
module.exports = React.createClass({
+ displayName: 'SearchBar',
componentDidMount: function() {
PostStore.addSearchTermChangeListener(this._onChange);
},
@@ -58,14 +59,14 @@ module.exports = React.createClass({
e.target.select();
},
performSearch: function(terms, isMentionSearch) {
- if (terms.length > 0) {
- $("#search-spinner").removeClass("hidden");
+ if (terms.length) {
+ this.setState({isSearching: true});
client.search(
terms,
function(data) {
- $("#search-spinner").addClass("hidden");
- if(utils.isMobile()) {
- $('#search')[0].value = "";
+ this.setState({isSearching: false});
+ if (utils.isMobile()) {
+ React.findDOMNode(this.refs.search).value = '';
}
AppDispatcher.handleServerAction({
@@ -73,18 +74,17 @@ module.exports = React.createClass({
results: data,
is_mention_search: isMentionSearch
});
- },
+ }.bind(this),
function(err) {
- $("#search-spinner").addClass("hidden");
- dispatchError(err, "search");
- }
+ this.setState({isSearching: false});
+ AsyncClient.dispatchError(err, "search");
+ }.bind(this)
);
}
},
handleSubmit: function(e) {
e.preventDefault();
- terms = this.state.search_term.trim();
- this.performSearch(terms);
+ this.performSearch(this.state.search_term.trim());
},
getInitialState: function() {
return getSearchTermStateFromStores();
@@ -92,11 +92,18 @@ module.exports = React.createClass({
render: function() {
return (
<div>
- <div className="sidebar__collapse" onClick={this.handleClose}></div>
+ <div className="sidebar__collapse" onClick={this.handleClose}>Cancel</div>
<span className="glyphicon glyphicon-search sidebar__search-icon"></span>
<form role="form" className="search__form relative-div" onSubmit={this.handleSubmit}>
- <input type="text" className="form-control search-bar-box" ref="search" id="search" placeholder="Search" value={this.state.search_term} onFocus={this.handleUserFocus} onChange={this.handleUserInput} />
- <span id="search-spinner" className="glyphicon glyphicon-refresh glyphicon-refresh-animate hidden"></span>
+ <input
+ type="text"
+ ref="search"
+ className="form-control search-bar-box"
+ placeholder="Search"
+ value={this.state.search_term}
+ onFocus={this.handleUserFocus}
+ onChange={this.handleUserInput} />
+ {this.state.isSearching ? <span className={"glyphicon glyphicon-refresh glyphicon-refresh-animate"}></span> : null}
</form>
</div>
);
diff --git a/web/react/components/search_results.jsx b/web/react/components/search_results.jsx
index 003a38b7e..643ad112b 100644
--- a/web/react/components/search_results.jsx
+++ b/web/react/components/search_results.jsx
@@ -8,12 +8,13 @@ var UserStore = require('../stores/user_store.jsx');
var UserProfile = require( './user_profile.jsx' );
var SearchBox =require('./search_bar.jsx');
var utils = require('../utils/utils.jsx');
-var client =require('../utils/client.jsx');
+var client = require('../utils/client.jsx');
+var AsyncClient = require('../utils/async_client.jsx');
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
-RhsHeaderSearch = React.createClass({
+var RhsHeaderSearch = React.createClass({
handleClose: function(e) {
e.preventDefault();
@@ -32,13 +33,13 @@ RhsHeaderSearch = React.createClass({
return (
<div className="sidebar--right__header">
<span className="sidebar--right__title">{title}</span>
- <button type="button" className="sidebar--right__close" aria-label="Close" onClick={this.handleClose}></button>
+ <button type="button" className="sidebar--right__close" aria-label="Close" title="Close" onClick={this.handleClose}></button>
</div>
);
}
});
-SearchItem = React.createClass({
+var SearchItem = React.createClass({
handleClick: function(e) {
e.preventDefault();
@@ -62,39 +63,41 @@ SearchItem = React.createClass({
});
},
function(err) {
- dispatchError(err, "getPost");
+ AsyncClient.dispatchError(err, "getPost");
}
);
- var postChannel = ChannelStore.get(this.props.post.channel_id);
- var teammate = postChannel.type === 'D' ? utils.getDirectTeammate(this.props.post.channel_id).username : "";
+ var postChannel = ChannelStore.get(this.props.post.channel_id);
+ var teammate = postChannel.type === 'D' ? utils.getDirectTeammate(this.props.post.channel_id).username : "";
- utils.switchChannel(postChannel,teammate);
+ utils.switchChannel(postChannel, teammate);
},
+
render: function() {
var message = utils.textToJsx(this.props.post.message, {searchTerm: this.props.term, noMentionHighlight: !this.props.isMentionSearch});
var channelName = "";
- var channel = ChannelStore.get(this.props.post.channel_id)
+ var channel = ChannelStore.get(this.props.post.channel_id);
+ var timestamp = UserStore.getCurrentUser().update_at;
if (channel) {
- if (channel.type === 'D') {
- channelName = "Private Message";
- } else {
- channelName = channel.display_name;
- }
+ channelName = (channel.type === 'D') ? "Private Message" : channel.display_name;
}
- return (
+ return (
<div className="search-item-container post" onClick={this.handleClick}>
<div className="search-channel__name">{ channelName }</div>
<div className="post-profile-img__container">
- <img className="post-profile-img" src={"/api/v1/users/" + this.props.post.user_id + "/image"} height="36" width="36" />
+ <img className="post-profile-img" src={"/api/v1/users/" + this.props.post.user_id + "/image?time=" + timestamp} height="36" width="36" />
</div>
<div className="post__content">
<ul className="post-header">
<li className="post-header-col"><strong><UserProfile userId={this.props.post.user_id} /></strong></li>
- <li className="post-header-col"><time className="search-item-time">{ utils.displayDate(this.props.post.create_at)+' '+utils.displayTime(this.props.post.create_at) }</time></li>
+ <li className="post-header-col">
+ <time className="search-item-time">
+ { utils.displayDate(this.props.post.create_at) + ' ' + utils.displayTime(this.props.post.create_at) }
+ </time>
+ </li>
</ul>
<div className="search-item-snippet"><span>{message}</span></div>
</div>
@@ -103,11 +106,13 @@ SearchItem = React.createClass({
}
});
+
function getStateFromStores() {
return { results: PostStore.getSearchResults() };
}
module.exports = React.createClass({
+ displayName: 'SearchResults',
componentDidMount: function() {
PostStore.addSearchChangeListener(this._onChange);
this.resize();
@@ -143,41 +148,24 @@ module.exports = React.createClass({
var results = this.state.results;
var currentId = UserStore.getCurrentId();
- var searchForm = currentId == null ? null : <SearchBox />;
-
- if (results == null) {
- return (
- <div className="sidebar--right__header">
- <div className="sidebar__heading">Search Results</div>
- </div>
- );
- }
+ var searchForm = currentId ? <SearchBox /> : null;
+ var noResults = (!results || !results.order || !results.order.length);
+ var searchTerm = PostStore.getSearchTerm();
- if (!results.order || results.order.length == 0) {
- return (
- <div className="sidebar--right__content">
- <div className="search-bar__container">{searchForm}</div>
- <div className="sidebar-right__body">
- <RhsHeaderSearch />
- <div id="search-items-container" className="search-items-container">
- <div className="sidebar--right__subheader">No results</div>
- </div>
- </div>
- </div>
- );
- }
-
- var self = this;
return (
<div className="sidebar--right__content">
<div className="search-bar__container sidebar--right__search-header">{searchForm}</div>
<div className="sidebar-right__body">
<RhsHeaderSearch isMentionSearch={this.props.isMentionSearch} />
<div id="search-items-container" className="search-items-container">
- {results.order.map(function(id) {
- var post = results.posts[id];
- return <SearchItem key={post.id} post={post} term={PostStore.getSearchTerm()} isMentionSearch={self.props.isMentionSearch} />
- })}
+
+ { noResults ? <div className="sidebar--right__subheader">No results</div>
+ : results.order.map(function(id) {
+ var post = results.posts[id];
+ return <SearchItem key={post.id} post={post} term={searchTerm} isMentionSearch={this.props.isMentionSearch} />
+ }, this)
+ }
+
</div>
</div>
</div>
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 2095978e8..3cf67e410 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -6,6 +6,7 @@ var ChannelStore = require('../stores/channel_store.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var SocketStore = require('../stores/socket_store.jsx');
var UserStore = require('../stores/user_store.jsx');
+var TeamStore = require('../stores/team_store.jsx');
var utils = require('../utils/utils.jsx');
var SidebarHeader = require('./sidebar_header.jsx');
var SearchBox = require('./search_bar.jsx');
@@ -13,93 +14,6 @@ var SearchBox = require('./search_bar.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
-var SidebarLoginForm = React.createClass({
- handleSubmit: function(e) {
- e.preventDefault();
- var state = { }
-
- var domain = this.refs.domain.getDOMNode().value.trim();
- if (!domain) {
- state.server_error = "A domain is required"
- this.setState(state);
- return;
- }
-
- var email = this.refs.email.getDOMNode().value.trim();
- if (!email) {
- state.server_error = "An email is required"
- this.setState(state);
- return;
- }
-
- var password = this.refs.password.getDOMNode().value.trim();
- if (!password) {
- state.server_error = "A password is required"
- this.setState(state);
- return;
- }
-
- state.server_error = "";
- this.setState(state);
-
- client.loginByEmail(domain, email, password,
- function(data) {
- UserStore.setLastDomain(domain);
- UserStore.setLastEmail(email);
- UserStore.setCurrentUser(data);
-
- var redirect = utils.getUrlParameter("redirect");
- if (redirect) {
- window.location.href = decodeURI(redirect);
- } else {
- window.location.href = '/channels/town-square';
- }
-
- }.bind(this),
- function(err) {
- if (err.message == "Login failed because email address has not been verified") {
- window.location.href = '/verify?domain=' + encodeURIComponent(domain) + '&email=' + encodeURIComponent(email);
- return;
- }
- state.server_error = err.message;
- this.valid = false;
- this.setState(state);
- }.bind(this)
- );
- },
- getInitialState: function() {
- return { };
- },
- render: function() {
- var server_error = this.state.server_error ? <label className="control-label">{this.state.server_error}</label> : null;
-
- var subDomain = utils.getSubDomain();
- var subDomainClass = "form-control hidden";
-
- if (subDomain == "") {
- subDomain = UserStore.getLastDomain();
- subDomainClass = "form-control";
- }
-
- return (
- <form className="" onSubmit={this.handleSubmit}>
- <a href="/find_team">{"Find your " + strings.Team}</a>
- <div className={server_error ? 'form-group has-error' : 'form-group'}>
- { server_error }
- <input type="text" className={subDomainClass} name="domain" defaultValue={subDomain} ref="domain" placeholder="Domain" />
- </div>
- <div className={server_error ? 'form-group has-error' : 'form-group'}>
- <input type="text" className="form-control" name="email" defaultValue={UserStore.getLastEmail()} ref="email" placeholder="Email" />
- </div>
- <div className={server_error ? 'form-group has-error' : 'form-group'}>
- <input type="password" className="form-control" name="password" ref="password" placeholder="Password" />
- </div>
- <button type="submit" className="btn btn-default">Login</button>
- </form>
- );
- }
-});
-
function getStateFromStores() {
var members = ChannelStore.getAllMembers();
var team_member_map = UserStore.getActiveOnlyProfiles();
@@ -131,7 +45,7 @@ function getStateFromStores() {
var channel = ChannelStore.getByName(channelName);
if (channel != null) {
- channel.display_name = teammate.full_name.trim() != "" ? teammate.full_name : teammate.username;
+ channel.display_name = utils.getDisplayName(teammate);
channel.teammate_username = teammate.username;
channel.status = UserStore.getStatus(teammate.id);
@@ -150,7 +64,7 @@ function getStateFromStores() {
var tempChannel = {};
tempChannel.fake = true;
tempChannel.name = channelName;
- tempChannel.display_name = teammate.full_name.trim() != "" ? teammate.full_name : teammate.username;
+ tempChannel.display_name = utils.getDisplayName(teammate);
tempChannel.status = UserStore.getStatus(teammate.id);
tempChannel.last_post_at = 0;
readDirectChannels.push(tempChannel);
@@ -192,7 +106,7 @@ function getStateFromStores() {
};
}
-var SidebarLoggedIn = React.createClass({
+module.exports = React.createClass({
componentDidMount: function() {
ChannelStore.addChangeListener(this._onChange);
UserStore.addChangeListener(this._onChange);
@@ -249,11 +163,27 @@ var SidebarLoggedIn = React.createClass({
var repRegex = new RegExp("<br>", "g");
var post = JSON.parse(msg.props.post);
- var msg = post.message.replace(repRegex, "\n").split("\n")[0].replace("<mention>", "").replace("</mention>", "");
+ var msgProps = msg.props;
+ var msg = post.message.replace(repRegex, "\n").replace(/\n+/g, " ").replace("<mention>", "").replace("</mention>", "");
+
if (msg.length > 50) {
msg = msg.substring(0,49) + "...";
}
- utils.notifyMe(title, username + " wrote: " + msg, channel);
+
+ if (msg.length === 0) {
+ if (msgProps.image) {
+ utils.notifyMe(title, username + " uploaded an image", channel);
+ }
+ else if (msgProps.otherFile) {
+ utils.notifyMe(title, username + " uploaded a file", channel);
+ }
+ else {
+ utils.notifyMe(title, username + " did something new", channel);
+ }
+ }
+ else {
+ utils.notifyMe(title, username + " wrote: " + msg, channel);
+ }
if (!user.notify_props || user.notify_props.desktop_sound === "true") {
utils.ding();
}
@@ -367,7 +297,7 @@ var SidebarLoggedIn = React.createClass({
);
} else {
return (
- <li key={channel.name} className={active}><a className={"sidebar-channel " + titleClass} href={"/channels/"+channel.name}><span className="status" dangerouslySetInnerHTML={{__html: statusIcon}} /> {badge}{channel.display_name}</a></li>
+ <li key={channel.name} className={active}><a className={"sidebar-channel " + titleClass} href={TeamStore.getCurrentTeamUrl() + "/channels/"+channel.name}><span className="status" dangerouslySetInnerHTML={{__html: statusIcon}} /> {badge}{channel.display_name}</a></li>
);
}
@@ -398,7 +328,7 @@ var SidebarLoggedIn = React.createClass({
}
return (
<div>
- <SidebarHeader teamName={this.props.teamName} teamType={this.props.teamType} />
+ <SidebarHeader teamDisplayName={this.props.teamDisplayName} teamType={this.props.teamType} />
<SearchBox />
<div className="nav-pills__container">
@@ -424,25 +354,3 @@ var SidebarLoggedIn = React.createClass({
);
}
});
-
-var SidebarLoggedOut = React.createClass({
- render: function() {
- return (
- <div>
- <SidebarHeader teamName={this.props.teamName} />
- <SidebarLoginForm />
- </div>
- );
- }
-});
-
-module.exports = React.createClass({
- render: function() {
- var currentId = UserStore.getCurrentId();
- if (currentId != null) {
- return <SidebarLoggedIn teamName={this.props.teamName} teamType={this.props.teamType} />;
- } else {
- return <SidebarLoggedOut teamName={this.props.teamName} />;
- }
- }
-});
diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx
index 0b59d2036..bab2897b6 100644
--- a/web/react/components/sidebar_header.jsx
+++ b/web/react/components/sidebar_header.jsx
@@ -37,17 +37,13 @@ var NavbarDropdown = React.createClass({
var invite_link = "";
var manage_link = "";
var rename_link = "";
- var currentUser = UserStore.getCurrentUser()
+ var currentUser = UserStore.getCurrentUser();
var isAdmin = false;
if (currentUser != null) {
isAdmin = currentUser.roles.indexOf("admin") > -1;
- invite_link = (
- <li>
- <a href="#" data-toggle="modal" data-target="#invite_member">Invite New Member</a>
- </li>
- );
+ invite_link = ( <li> <a href="#" data-toggle="modal" data-target="#invite_member">Invite New Member</a> </li>);
if (this.props.teamType == "O") {
team_link = (
@@ -59,43 +55,31 @@ var NavbarDropdown = React.createClass({
}
if (isAdmin) {
- manage_link = (
- <li>
- <a href="#" data-toggle="modal" data-target="#team_members">Manage Team</a>
- </li>
- );
- rename_link = (
- <li>
- <a href="#" data-toggle="modal" data-target="#rename_team_link">Rename</a>
- </li>
- );
+ manage_link = ( <li> <a href="#" data-toggle="modal" data-target="#team_members">Manage Team</a> </li>);
+ rename_link = ( <li> <a href="#" data-toggle="modal" data-target="#rename_team_link">Rename</a> </li>);
}
var teams = [];
+ teams.push(<li className="divider" key="div"></li>);
if (this.state.teams.length > 1) {
for (var i = 0; i < this.state.teams.length; i++) {
- var domain = this.state.teams[i];
-
- if (domain == utils.getSubDomain())
- continue;
+ var teamName = this.state.teams[i];
- if (teams.length == 0)
- teams.push(<li className="divider" key="div"></li>);
-
- teams.push(<li key={ domain }><a href={window.location.protocol + "//" + domain + "." + utils.getDomainWithOutSub() }>Switch to { domain }</a></li>);
+ teams.push(<li key={ teamName }><a href={window.location.origin + "/" + teamName }>Switch to { teamName }</a></li>);
}
}
+ teams.push(<li><a href={window.location.origin + "/signup_team" }>Create a New Team</a></li>);
return (
<ul className="nav navbar-nav navbar-right">
<li className="dropdown">
<a href="#" className="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
- <i className="dropdown__icon"></i>
+ <span className="dropdown__icon" dangerouslySetInnerHTML={{__html: " <svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='4px' height='16px' viewBox='0 0 8 32' enable-background='new 0 0 8 32' xml:space='preserve'> <g> <circle cx='4' cy='4.062' r='4'/> <circle cx='4' cy='16' r='4'/> <circle cx='4' cy='28' r='4'/> </g> </svg>"}} />
</a>
<ul className="dropdown-menu" role="menu">
<li><a href="#" data-toggle="modal" data-target="#user_settings1">Account Settings</a></li>
- { isAdmin ? <li><a href="#" data-toggle="modal" data-target="#team_settings">Team Settings</a></li> : "" }
+ { isAdmin ? <li><a href="#" data-toggle="modal" data-target="#team_settings">Team Settings</a></li> : null }
{ invite_link }
{ team_link }
{ manage_link }
@@ -113,23 +97,32 @@ var NavbarDropdown = React.createClass({
});
module.exports = React.createClass({
- handleSubmit: function(e) {
- e.preventDefault();
- },
- getInitialState: function() {
- return { };
+ displayName: 'SidebarHeader',
+
+ getDefaultProps: function() {
+ return {
+ teamName: config.SiteName
+ };
},
+
render: function() {
- var teamName = this.props.teamName ? this.props.teamName : config.SiteName;
+ var teamDisplayName = this.props.teamDisplayName ? this.props.teamDisplayName : config.SiteName;
+ var me = UserStore.getCurrentUser()
+ if (!me) {
+ return null;
+ }
return (
<div className="team__header theme">
- <a className="team__name" href="/channels/town-square">{ teamName }</a>
+ <a className="settings_link" href="#" data-toggle="modal" data-target="#user_settings1">
+ <img className="user__picture" src={"/api/v1/users/" + me.id + "/image?time=" + me.update_at} />
+ <div className="header__info">
+ <div className="user__name">{ '@' + me.username}</div>
+ <div className="team__name">{ teamDisplayName }</div>
+ </div>
+ </a>
<NavbarDropdown teamType={this.props.teamType} />
</div>
);
}
});
-
-
-
diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx
index 22d1d9ad2..15306a499 100644
--- a/web/react/components/sidebar_right_menu.jsx
+++ b/web/react/components/sidebar_right_menu.jsx
@@ -49,12 +49,12 @@ module.exports = React.createClass({
}
var siteName = config.SiteName != null ? config.SiteName : "";
- var teamName = this.props.teamName ? this.props.teamName : siteName;
+ var teamDisplayName = this.props.teamDisplayName ? this.props.teamDisplayName : siteName;
return (
<div>
<div className="team__header theme">
- <a className="team__name" href="/channels/town-square">{ teamName }</a>
+ <a className="team__name" href="/channels/town-square">{ teamDisplayName }</a>
</div>
<div className="nav-pills__container">
diff --git a/web/react/components/signup_team.jsx b/web/react/components/signup_team.jsx
index 22086250c..cf982cc1e 100644
--- a/web/react/components/signup_team.jsx
+++ b/web/react/components/signup_team.jsx
@@ -20,8 +20,8 @@ module.exports = React.createClass({
state.email_error = "";
}
- team.name = this.refs.name.getDOMNode().value.trim();
- if (!team.name) {
+ team.display_name = this.refs.name.getDOMNode().value.trim();
+ if (!team.display_name) {
state.name_error = "This field is required";
state.inValid = true;
}
@@ -34,7 +34,7 @@ module.exports = React.createClass({
return;
}
- client.signupTeam(team.email, team.name,
+ client.signupTeam(team.email, team.display_name,
function(data) {
if (data["follow_link"]) {
window.location.href = data["follow_link"];
@@ -61,7 +61,7 @@ module.exports = React.createClass({
return (
<form role="form" onSubmit={this.handleSubmit}>
<div className={ email_error ? "form-group has-error" : "form-group" }>
- <input type="email" ref="email" className="form-control" placeholder="Email Address" maxLength="128" />
+ <input autoFocus={true} type="email" ref="email" className="form-control" placeholder="Email Address" maxLength="128" />
{ email_error }
</div>
<div className={ name_error ? "form-group has-error" : "form-group" }>
@@ -70,6 +70,9 @@ module.exports = React.createClass({
</div>
{ server_error }
<button className="btn btn-md btn-primary" type="submit">Sign up for Free</button>
+ <div className="form-group form-group--small">
+ <span><a href="/find_team">{"Find my " + strings.Team}</a></span>
+ </div>
</form>
);
}
diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx
index 500ee231e..9ceeb6324 100644
--- a/web/react/components/signup_team_complete.jsx
+++ b/web/react/components/signup_team_complete.jsx
@@ -15,7 +15,7 @@ WelcomePage = React.createClass({
return;
}
e.preventDefault();
- this.props.state.wizard = "team_name";
+ this.props.state.wizard = "team_display_name";
this.props.updateParent(this.props.state);
},
handleDiffEmail: function (e) {
@@ -57,6 +57,17 @@ WelcomePage = React.createClass({
getInitialState: function() {
return { use_diff: false };
},
+ handleKeyPress: function(event) {
+ if (event.keyCode == 13) {
+ this.submitNext(event);
+ }
+ },
+ componentWillMount: function() {
+ document.addEventListener("keyup", this.handleKeyPress, false);
+ },
+ componentWillUnmount: function() {
+ document.removeEventListener("keyup", this.handleKeyPress, false);
+ },
render: function() {
client.track('signup', 'signup_team_01_welcome');
@@ -77,7 +88,7 @@ WelcomePage = React.createClass({
<span className="black">{ this.props.state.team.email }</span><br />
</p>
<div className="form-group">
- <button className="btn-primary btn form-group" onClick={this.submitNext}><i className="glyphicon glyphicon-ok"></i>Yes, this address is correct</button>
+ <button className="btn-primary btn form-group" type="submit" onClick={this.submitNext}><i className="glyphicon glyphicon-ok"></i>Yes, this address is correct</button>
{ storage_error }
</div>
<hr />
@@ -92,15 +103,15 @@ WelcomePage = React.createClass({
{ email_error }
</div>
{ server_error }
- <button className="btn btn-md btn-primary" onClick={this.handleDiffSubmit} type="submit">Use this instead</button>
+ <button className="btn btn-md btn-primary" type="button" onClick={this.handleDiffSubmit} type="submit">Use this instead</button>
</div>
- <button onClick={this.handleDiffEmail} className={ this.state.use_diff ? "btn-default btn hidden" : "btn-default btn" }>Use a different address</button>
+ <button type="button" onClick={this.handleDiffEmail} className={ this.state.use_diff ? "btn-default btn hidden" : "btn-default btn" }>Use a different address</button>
</div>
);
}
});
-TeamNamePage = React.createClass({
+TeamDisplayNamePage = React.createClass({
submitBack: function (e) {
e.preventDefault();
this.props.state.wizard = "welcome";
@@ -109,19 +120,24 @@ TeamNamePage = React.createClass({
submitNext: function (e) {
e.preventDefault();
- var name = this.refs.name.getDOMNode().value.trim();
- if (!name) {
+ var display_name = this.refs.name.getDOMNode().value.trim();
+ if (!display_name) {
this.setState({name_error: "This field is required"});
return;
}
this.props.state.wizard = "team_url";
- this.props.state.team.name = name;
+ this.props.state.team.display_name = display_name;
this.props.updateParent(this.props.state);
},
getInitialState: function() {
return { };
},
+ handleFocus: function(e) {
+ e.preventDefault();
+
+ e.currentTarget.select();
+ },
render: function() {
client.track('signup', 'signup_team_02_name');
@@ -130,29 +146,31 @@ TeamNamePage = React.createClass({
return (
<div>
+ <form>
<img className="signup-team-logo" src="/static/images/logo.png" />
<h2>{utils.toTitleCase(strings.Team) + " Name"}</h2>
<div className={ name_error ? "form-group has-error" : "form-group" }>
<div className="row">
<div className="col-sm-9">
- <input type="text" ref="name" className="form-control" placeholder="" maxLength="128" defaultValue={this.props.state.team.name} />
+ <input type="text" ref="name" className="form-control" placeholder="" maxLength="128" defaultValue={this.props.state.team.display_name} autoFocus={true} onFocus={this.handleFocus} />
</div>
</div>
{ name_error }
</div>
<p>{"Your " + strings.Team + " name shows in menus and headings. It may include the name of your " + strings.Company + ", but it's not required."}</p>
- <button className="btn btn-default" onClick={this.submitBack}><i className="glyphicon glyphicon-chevron-left"></i> Back</button>&nbsp;
- <button className="btn-primary btn" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button>
+ <button type="button" className="btn btn-default" onClick={this.submitBack}><i className="glyphicon glyphicon-chevron-left"></i> Back</button>&nbsp;
+ <button type="submit" className="btn-primary btn" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button>
+ </form>
</div>
);
}
});
-TeamUrlPage = React.createClass({
+TeamURLPage = React.createClass({
submitBack: function (e) {
e.preventDefault();
- this.props.state.wizard = "team_name";
+ this.props.state.wizard = "team_display_name";
this.props.updateParent(this.props.state);
},
submitNext: function (e) {
@@ -172,18 +190,18 @@ TeamUrlPage = React.createClass({
return;
}
else if (cleaned_name.length <= 3 || cleaned_name.length > 15) {
- this.setState({name_error: "Domain must be 4 or more characters up to a maximum of 15"})
+ this.setState({name_error: "Name must be 4 or more characters up to a maximum of 15"})
return;
}
- for (var index = 0; index < constants.RESERVED_DOMAINS.length; index++) {
- if (cleaned_name.indexOf(constants.RESERVED_DOMAINS[index]) == 0) {
- this.setState({name_error: "This Team URL name is unavailable"})
+ for (var index = 0; index < constants.RESERVED_TEAM_NAMES.length; index++) {
+ if (cleaned_name.indexOf(constants.RESERVED_TEAM_NAMES[index]) == 0) {
+ this.setState({name_error: "This team name is unavailable"})
return;
}
}
- client.findTeamByDomain(name,
+ client.findTeamByName(name,
function(data) {
if (!data) {
if (config.AllowSignupDomainsWizard) {
@@ -193,7 +211,7 @@ TeamUrlPage = React.createClass({
this.props.state.team.type = 'O';
}
- this.props.state.team.domain = name;
+ this.props.state.team.name = name;
this.props.updateParent(this.props.state);
}
else {
@@ -210,6 +228,11 @@ TeamUrlPage = React.createClass({
getInitialState: function() {
return { };
},
+ handleFocus: function(e) {
+ e.preventDefault();
+
+ e.currentTarget.select();
+ },
render: function() {
client.track('signup', 'signup_team_03_url');
@@ -218,14 +241,15 @@ TeamUrlPage = React.createClass({
return (
<div>
+ <form>
<img className="signup-team-logo" src="/static/images/logo.png" />
<h2>{utils.toTitleCase(strings.Team) + " URL"}</h2>
<div className={ name_error ? "form-group has-error" : "form-group" }>
<div className="row">
<div className="col-sm-9">
<div className="input-group">
- <input type="text" ref="name" className="form-control text-right" placeholder="" maxLength="128" defaultValue={this.props.state.team.domain} />
- <span className="input-group-addon">.{ utils.getDomainWithOutSub() }</span>
+ <span className="input-group-addon">{ window.location.origin + "/" }</span>
+ <input type="text" ref="name" className="form-control" placeholder="" maxLength="128" defaultValue={this.props.state.team.name} autoFocus={true} onFocus={this.handleFocus}/>
</div>
</div>
</div>
@@ -233,8 +257,9 @@ TeamUrlPage = React.createClass({
</div>
<p className="black">{"Pick something short and memorable for your " + strings.Team + "'s web address."}</p>
<p>{"Your " + strings.Team + " URL can only contain lowercase letters, numbers and dashes. Also, it needs to start with a letter and cannot end in a dash."}</p>
- <button className="btn btn-default" onClick={this.submitBack}><i className="glyphicon glyphicon-chevron-left"></i> Back</button>&nbsp;
- <button className="btn-primary btn" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button>
+ <button type="button" className="btn btn-default" onClick={this.submitBack}><i className="glyphicon glyphicon-chevron-left"></i> Back</button>&nbsp;
+ <button type="submit" className="btn-primary btn" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button>
+ </form>
</div>
);
}
@@ -291,6 +316,7 @@ AllowedDomainsPage = React.createClass({
return (
<div>
+ <form>
<img className="signup-team-logo" src="/static/images/logo.png" />
<h2>Email Domain</h2>
<p>
@@ -303,7 +329,7 @@ AllowedDomainsPage = React.createClass({
<div className="col-sm-9">
<div className="input-group">
<span className="input-group-addon">@</span>
- <input type="text" ref="name" className="form-control" placeholder="" maxLength="128" defaultValue={this.props.state.team.allowed_domains} />
+ <input type="text" ref="name" className="form-control" placeholder="" maxLength="128" defaultValue={this.props.state.team.allowed_domains} autoFocus={true} onFocus={this.handleFocus}/>
</div>
</div>
</div>
@@ -313,8 +339,9 @@ AllowedDomainsPage = React.createClass({
<p>
<div className="checkbox"><label><input type="checkbox" ref="open_network" defaultChecked={this.props.state.team.type == 'O'} /> Allow anyone to signup to this domain without an invitation.</label></div>
</p>
- <button className="btn btn-default" onClick={this.submitBack}><i className="glyphicon glyphicon-chevron-left"></i> Back</button>&nbsp;
- <button className="btn-primary btn" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button>
+ <button type="button" className="btn btn-default" onClick={this.submitBack}><i className="glyphicon glyphicon-chevron-left"></i> Back</button>&nbsp;
+ <button type="submit" className="btn-primary btn" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button>
+ </form>
</div>
);
}
@@ -356,7 +383,7 @@ EmailItem = React.createClass({
return (
<div className={ email_error ? "form-group has-error" : "form-group" }>
- <input type="email" ref="email" className="form-control" placeholder="Email Address" defaultValue={this.props.email} maxLength="128" />
+ <input autoFocus={this.props.focus} type="email" ref="email" className="form-control" placeholder="Email Address" defaultValue={this.props.email} maxLength="128" />
{ email_error }
</div>
);
@@ -424,16 +451,22 @@ SendInivtesPage = React.createClass({
var emails = [];
for (var i = 0; i < this.props.state.invites.length; i++) {
- emails.push(<EmailItem key={i} ref={'email_' + i} email={this.props.state.invites[i]} />);
+ if (i == 0) {
+ emails.push(<EmailItem focus={true} key={i} ref={'email_' + i} email={this.props.state.invites[i]} />);
+ } else {
+ emails.push(<EmailItem focus={false} key={i} ref={'email_' + i} email={this.props.state.invites[i]} />);
+ }
}
return (
<div>
+ <form>
<img className="signup-team-logo" src="/static/images/logo.png" />
<h2>Send Invitations</h2>
{ emails }
- <div className="form-group"><button className="btn-default btn" onClick={this.submitAddInvite}>Add Invitation</button></div>
- <div className="form btn-default-group"><button className="btn btn-default" onClick={this.submitBack}><i className="glyphicon glyphicon-chevron-left"></i> Back</button>&nbsp;<button className="btn-primary btn" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button></div>
+ <div className="form-group"><button type="button" className="btn-default btn" onClick={this.submitAddInvite}>Add Invitation</button></div>
+ <div className="form btn-default-group"><button type="button" className="btn btn-default" onClick={this.submitBack}><i className="glyphicon glyphicon-chevron-left"></i> Back</button>&nbsp;<button type="submit" className="btn-primary btn" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button></div>
+ </form>
<p>{"If you'd prefer, you can send invitations after you finish setting up the "+ strings.Team + "."}</p>
<div><a href="#" onClick={this.submitSkip}>Skip this step</a></div>
</div>
@@ -477,20 +510,22 @@ UsernamePage = React.createClass({
return (
<div>
+ <form>
<img className="signup-team-logo" src="/static/images/logo.png" />
<h2>Choose a username</h2>
<div className={ name_error ? "form-group has-error" : "form-group" }>
<div className="row">
<div className="col-sm-9">
- <input type="text" ref="name" className="form-control" placeholder="" defaultValue={this.props.state.user.username} maxLength="128" />
+ <input autoFocus={true} type="text" ref="name" className="form-control" placeholder="" defaultValue={this.props.state.user.username} maxLength="128" />
</div>
</div>
{ name_error }
</div>
<p>{"Pick something " + strings.Team + "mates will recognize. Your username is how you will appear to others."}</p>
<p>It can be made of lowercase letters and numbers.</p>
- <button className="btn btn-default" onClick={this.submitBack}><i className="glyphicon glyphicon-chevron-left"></i> Back</button>&nbsp;
- <button className="btn-primary btn" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button>
+ <button type="button" className="btn btn-default" onClick={this.submitBack}><i className="glyphicon glyphicon-chevron-left"></i> Back</button>&nbsp;
+ <button type="submit" className="btn-primary btn" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button>
+ </form>
</div>
);
}
@@ -531,18 +566,11 @@ PasswordPage = React.createClass({
props.state.wizard = "finished";
props.updateParent(props.state, true);
- if (utils.isTestDomain()) {
- UserStore.setLastDomain(teamSignup.team.domain);
- UserStore.setLastEmail(teamSignup.team.email);
- window.location.href = window.location.protocol + '//' + utils.getDomainWithOutSub() + '/login?email=' + encodeURIComponent(teamSignup.team.email);
- }
- else {
- window.location.href = window.location.protocol + '//' + teamSignup.team.domain + '.' + utils.getDomainWithOutSub() + '/login?email=' + encodeURIComponent(teamSignup.team.email);
- }
+ window.location.href = window.location.origin + '/' + props.state.team.name + '/login?email=' + encodeURIComponent(teamSignup.team.email);
// client.loginByEmail(teamSignup.team.domain, teamSignup.team.email, teamSignup.user.password,
// function(data) {
- // UserStore.setLastDomain(teamSignup.team.domain);
+ // TeamStore.setLastName(teamSignup.team.domain);
// UserStore.setLastEmail(teamSignup.team.email);
// UserStore.setCurrentUser(data);
// window.location.href = '/channels/town-square';
@@ -570,13 +598,14 @@ PasswordPage = React.createClass({
return (
<div>
+ <form>
<img className="signup-team-logo" src="/static/images/logo.png" />
<h2>Choose a password</h2>
<p>You'll use your email address ({this.props.state.team.email}) and password to log into {config.SiteName}.</p>
<div className={ name_error ? "form-group has-error" : "form-group" }>
<div className="row">
<div className="col-sm-9">
- <input type="password" ref="password" className="form-control" placeholder="" maxLength="128" />
+ <input autoFocus={true} type="password" ref="password" className="form-control" placeholder="" maxLength="128" />
</div>
</div>
{ name_error }
@@ -585,10 +614,11 @@ PasswordPage = React.createClass({
<label><input type="checkbox" ref="email_service" /> It's ok to send me occassional email with updates about the {config.SiteName} service.</label>
</div>
<div className="form-group">
- <button className="btn btn-default" onClick={this.submitBack}><i className="glyphicon glyphicon-chevron-left"></i> Back</button>&nbsp;
- <button className="btn-primary btn" id="finish-button" data-loading-text={"<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> Creating "+strings.Team+"..."} onClick={this.submitNext}>Finish</button>
+ <button type="button" className="btn btn-default" onClick={this.submitBack}><i className="glyphicon glyphicon-chevron-left"></i> Back</button>&nbsp;
+ <button type="submit" className="btn-primary btn" id="finish-button" data-loading-text={"<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> Creating "+strings.Team+"..."} onClick={this.submitNext}>Finish</button>
</div>
<p>By proceeding to create your account and use { config.SiteName }, you agree to our <a href={ config.TermsLink }>Terms of Service</a> and <a href={ config.PrivacyLink }>Privacy Policy</a>. If you do not agree, you cannot use {config.SiteName}.</p>
+ </form>
</div>
);
}
@@ -596,28 +626,23 @@ PasswordPage = React.createClass({
module.exports = React.createClass({
updateParent: function(state, skipSet) {
- BrowserStore.setGlobalItem(this.props.hash, JSON.stringify(state));
+ BrowserStore.setGlobalItem(this.props.hash, state);
if (!skipSet) {
this.setState(state);
}
},
getInitialState: function() {
- var props = null;
- try {
- props = JSON.parse(BrowserStore.getGlobalItem(this.props.hash));
- }
- catch(parse_error) {
- }
+ var props = BrowserStore.getGlobalItem(this.props.hash);
if (!props) {
props = {};
props.wizard = "welcome";
props.team = {};
props.team.email = this.props.email;
- props.team.name = this.props.name;
+ props.team.display_name = this.props.name;
props.team.company_name = this.props.name;
- props.team.domain = utils.cleanUpUrlable(this.props.name);
+ props.team.name = utils.cleanUpUrlable(this.props.name);
props.team.allowed_domains = "";
props.invites = [];
props.invites.push("");
@@ -628,19 +653,19 @@ module.exports = React.createClass({
props.data = this.props.data;
}
- return props ;
+ return props;
},
render: function() {
if (this.state.wizard == "welcome") {
return <WelcomePage state={this.state} updateParent={this.updateParent} />
}
- if (this.state.wizard == "team_name") {
- return <TeamNamePage state={this.state} updateParent={this.updateParent} />
+ if (this.state.wizard == "team_display_name") {
+ return <TeamDisplayNamePage state={this.state} updateParent={this.updateParent} />
}
if (this.state.wizard == "team_url") {
- return <TeamUrlPage state={this.state} updateParent={this.updateParent} />
+ return <TeamURLPage state={this.state} updateParent={this.updateParent} />
}
if (this.state.wizard == "allowed_domains") {
diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx
index fb96cc99f..eed323d1f 100644
--- a/web/react/components/signup_user_complete.jsx
+++ b/web/react/components/signup_user_complete.jsx
@@ -17,7 +17,7 @@ module.exports = React.createClass({
return;
}
- var username_error = utils.isValidUsername(this.state.user.username)
+ var username_error = utils.isValidUsername(this.state.user.username);
if (username_error === "Cannot use a reserved word as a username.") {
this.setState({name_error: "This username is reserved, please choose a new one.", email_error: "", password_error: "", server_error: ""});
return;
@@ -46,25 +46,25 @@ module.exports = React.createClass({
function(data) {
client.track('signup', 'signup_user_02_complete');
- if (data.email_verified) {
- client.loginByEmail(this.props.domain, this.state.user.email, this.state.user.password,
- function(data) {
- UserStore.setLastDomain(this.props.domain);
- UserStore.setLastEmail(this.state.user.email);
- UserStore.setCurrentUser(data);
- if (this.props.hash > 0)
- BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: "finished"}));
- window.location.href = '/channels/town-square';
- }.bind(this),
- function(err) {
+ client.loginByEmail(this.props.domain, this.state.user.email, this.state.user.password,
+ function(data) {
+ UserStore.setLastEmail(this.state.user.email);
+ UserStore.setCurrentUser(data);
+ if (this.props.hash > 0)
+ {
+ BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: "finished"}));
+ }
+ window.location.href = '/';
+ }.bind(this),
+ function(err) {
+ if (err.message == "Login failed because email address has not been verified") {
+ window.location.href = "/verify_email?email="+ encodeURIComponent(this.state.user.email) + "&domain=" + encodeURIComponent(this.props.domain);
+ } else {
this.state.server_error = err.message;
this.setState(this.state);
- }.bind(this)
- );
- }
- else {
- window.location.href = "/verify?email="+ encodeURIComponent(this.state.user.email) + "&domain=" + encodeURIComponent(this.props.domain);
- }
+ }
+ }.bind(this)
+ );
}.bind(this),
function(err) {
this.state.server_error = err.message;
@@ -73,12 +73,7 @@ module.exports = React.createClass({
);
},
getInitialState: function() {
- var props = null;
- try {
- props = JSON.parse(BrowserStore.getGlobalItem(this.props.hash));
- }
- catch(parse_error) {
- }
+ var props = BrowserStore.getGlobalItem(this.props.hash);
if (!props) {
props = {};
@@ -91,7 +86,7 @@ module.exports = React.createClass({
props.original_email = this.props.email;
}
- return props ;
+ return props;
},
render: function() {
diff --git a/web/react/components/team_members.jsx b/web/react/components/team_members.jsx
index 6b978f88b..616fd2c99 100644
--- a/web/react/components/team_members.jsx
+++ b/web/react/components/team_members.jsx
@@ -57,7 +57,7 @@ module.exports = React.createClass({
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" data-dismiss="modal" aria-label="Close" data-reactid=".5.0.0.0.0"><span aria-hidden="true" data-reactid=".5.0.0.0.0.0">×</span></button>
- <h4 className="modal-title" id="myModalLabel">{this.props.teamName + " Members"}</h4>
+ <h4 className="modal-title" id="myModalLabel">{this.props.teamDisplayName + " Members"}</h4>
</div>
<div ref="modalBody" className="modal-body">
<div className="channel-settings">
diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx
index 648960471..65f025919 100644
--- a/web/react/components/user_profile.jsx
+++ b/web/react/components/user_profile.jsx
@@ -53,7 +53,7 @@ module.exports = React.createClass({
var name = this.props.overwriteName ? this.props.overwriteName : this.state.profile.username;
- var data_content = "<img style='margin: 10px' src='/api/v1/users/" + this.state.profile.id + "/image' height='128' width='128' />";
+ var data_content = "<img class='user-popover__image' src='/api/v1/users/" + this.state.profile.id + "/image?time=" + this.state.profile.update_at + "' height='128' width='128' />";
if (!config.ShowEmail) {
data_content += "<div class='text-nowrap'>Email not shared</div>";
} else {
@@ -61,7 +61,7 @@ module.exports = React.createClass({
}
return (
- <div style={{"cursor" : "pointer", "display" : "inline-block"}} className="user-popover" id={"profile_" + this.uniqueId} data-toggle="popover" data-content={data_content} data-original-title={this.state.profile.username} >
+ <div className="user-popover" id={"profile_" + this.uniqueId} data-toggle="popover" data-content={data_content} data-original-title={this.state.profile.username} >
{ name }
</div>
);
diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx
index b4c3747af..59c97c309 100644
--- a/web/react/components/user_settings.jsx
+++ b/web/react/components/user_settings.jsx
@@ -156,6 +156,8 @@ var NotificationsTab = React.createClass({
var self = this;
+ var user = this.props.user;
+
var desktopSection;
if (this.props.activeSection === 'desktop') {
var notifyActive = [false, false, false];
@@ -314,20 +316,14 @@ var NotificationsTab = React.createClass({
var keysSection;
if (this.props.activeSection === 'keys') {
- var user = this.props.user;
- var first_name = "";
- if (user.full_name.length > 0) {
- first_name = user.full_name.split(' ')[0];
- }
-
var inputs = [];
- if (first_name != "") {
+ if (user.first_name) {
inputs.push(
<div>
<div className="checkbox">
<label>
- <input type="checkbox" checked={this.state.first_name_key} onChange={function(e){self.updateFirstNameKey(e.target.checked);}}>{'Your case sensitive first name "' + first_name + '"'}</input>
+ <input type="checkbox" checked={this.state.first_name_key} onChange={function(e){self.updateFirstNameKey(e.target.checked);}}>{'Your case sensitive first name "' + user.first_name + '"'}</input>
</label>
</div>
</div>
@@ -396,14 +392,9 @@ var NotificationsTab = React.createClass({
);
} else {
var keys = [];
- if (this.state.first_name_key) {
- var first_name = "";
- var user = this.props.user;
- if (user.full_name.length > 0) first_name = user.full_name.split(' ')[0];
- if (first_name != "") keys.push(first_name);
- }
- if (this.state.username_key) keys.push(this.props.user.username);
- if (this.state.mention_key) keys.push('@'+this.props.user.username);
+ if (this.state.first_name_key) keys.push(user.first_name);
+ if (this.state.username_key) keys.push(user.username);
+ if (this.state.mention_key) keys.push('@'+user.username);
if (this.state.all_key) keys.push('@all');
if (this.state.channel_key) keys.push('@channel');
if (this.state.custom_keys.length > 0) keys = keys.concat(this.state.custom_keys.split(','));
@@ -560,7 +551,7 @@ var AuditTab = React.createClass({
<div className="user-settings">
<h3 className="tab-header">Activity Log</h3>
<div className="divider-dark first"/>
- <div className="table-responsive" style={{ maxWidth: "560px", maxHeight: "300px" }}>
+ <div className="table-responsive">
<table className="table-condensed small">
<thead>
<tr>
@@ -576,11 +567,11 @@ var AuditTab = React.createClass({
this.state.audits.map(function(value, index) {
return (
<tr key={ "" + index }>
- <td style={{ whiteSpace: "nowrap" }}>{ new Date(value.create_at).toLocaleString() }</td>
- <td style={{ whiteSpace: "nowrap" }}>{ value.action.replace("/api/v1", "") }</td>
- <td style={{ whiteSpace: "nowrap" }}>{ value.ip_address }</td>
- <td style={{ whiteSpace: "nowrap" }}>{ value.session_id }</td>
- <td style={{ whiteSpace: "nowrap" }}>{ value.extra_info }</td>
+ <td className="text-nowrap">{ new Date(value.create_at).toLocaleString() }</td>
+ <td className="text-nowrap">{ value.action.replace("/api/v1", "") }</td>
+ <td className="text-nowrap">{ value.ip_address }</td>
+ <td className="text-nowrap">{ value.session_id }</td>
+ <td className="text-nowrap">{ value.extra_info }</td>
</tr>
);
}, this)
@@ -626,7 +617,7 @@ var SecurityTab = React.createClass({
client.updatePassword(data,
function(data) {
- this.updateSection("");
+ this.props.updateSection("");
AsyncClient.getMe();
this.setState({ current_password: '', new_password: '', confirm_password: '' });
}.bind(this),
@@ -752,6 +743,21 @@ var GeneralTab = React.createClass({
this.submitUser(user);
},
+ submitNickname: function(e) {
+ e.preventDefault();
+
+ var user = UserStore.getCurrentUser();
+ var nickname = this.state.nickname.trim();
+
+ if (user.nickname === nickname) {
+ this.setState({client_error: "You must submit a new nickname"})
+ return;
+ }
+
+ user.nickname = nickname;
+
+ this.submitUser(user);
+ },
submitName: function(e) {
e.preventDefault();
@@ -759,14 +765,13 @@ var GeneralTab = React.createClass({
var firstName = this.state.first_name.trim();
var lastName = this.state.last_name.trim();
- var fullName = firstName + ' ' + lastName;
-
- if (user.full_name === fullName) {
- this.setState({client_error: "You must submit a new name"})
+ if (user.first_name === firstName && user.last_name === lastName) {
+ this.setState({client_error: "You must submit a new first or last name"})
return;
}
- user.full_name = fullName;
+ user.first_name = firstName;
+ user.last_name = lastName;
this.submitUser(user);
},
@@ -820,6 +825,7 @@ var GeneralTab = React.createClass({
client.uploadProfileImage(formData,
function(data) {
this.submitActive = false;
+ AsyncClient.getMe();
window.location.reload();
}.bind(this),
function(err) {
@@ -838,6 +844,9 @@ var GeneralTab = React.createClass({
updateLastName: function(e) {
this.setState({ last_name: e.target.value});
},
+ updateNickname: function(e) {
+ this.setState({nickname: e.target.value});
+ },
updateEmail: function(e) {
this.setState({ email: e.target.value});
},
@@ -860,11 +869,7 @@ var GeneralTab = React.createClass({
getInitialState: function() {
var user = this.props.user;
- var splitStr = user.full_name.split(' ');
- var firstName = splitStr.shift();
- var lastName = splitStr.join(' ');
-
- return { username: user.username, first_name: firstName, last_name: lastName,
+ return { username: user.username, first_name: user.first_name, last_name: user.last_name, nickname: user.nickname,
email: user.email, picture: null };
},
render: function() {
@@ -900,7 +905,7 @@ var GeneralTab = React.createClass({
nameSection = (
<SettingItemMax
- title="Name"
+ title="Full Name"
inputs={inputs}
submit={this.submitName}
server_error={server_error}
@@ -909,15 +914,58 @@ var GeneralTab = React.createClass({
/>
);
} else {
+ var full_name = "";
+
+ if (user.first_name && user.last_name) {
+ full_name = user.first_name + " " + user.last_name;
+ } else if (user.first_name) {
+ full_name = user.first_name;
+ } else if (user.last_name) {
+ full_name = user.last_name;
+ }
+
nameSection = (
<SettingItemMin
- title="Name"
- describe={UserStore.getCurrentUser().full_name}
+ title="Full Name"
+ describe={full_name}
updateSection={function(){self.updateSection("name");}}
/>
);
}
+ var nicknameSection;
+ if (this.props.activeSection === 'nickname') {
+ var inputs = [];
+
+ inputs.push(
+ <div className="form-group">
+ <label className="col-sm-5 control-label">{utils.isMobile() ? "": "Nickname"}</label>
+ <div className="col-sm-7">
+ <input className="form-control" type="text" onChange={this.updateNickname} value={this.state.nickname}/>
+ </div>
+ </div>
+ );
+
+ nicknameSection = (
+ <SettingItemMax
+ title="Nickname"
+ inputs={inputs}
+ submit={this.submitNickname}
+ server_error={server_error}
+ client_error={client_error}
+ updateSection={function(e){self.updateSection("");e.preventDefault();}}
+ />
+ );
+ } else {
+ nicknameSection = (
+ <SettingItemMin
+ title="Nickname"
+ describe={UserStore.getCurrentUser().nickname}
+ updateSection={function(){self.updateSection("nickname");}}
+ />
+ );
+ }
+
var usernameSection;
if (this.props.activeSection === 'username') {
var inputs = [];
@@ -989,7 +1037,7 @@ var GeneralTab = React.createClass({
<SettingPicture
title="Profile Picture"
submit={this.submitPicture}
- src={"/api/v1/users/" + user.id + "/image"}
+ src={"/api/v1/users/" + user.id + "/image?time=" + user.last_picture_update}
server_error={server_error}
client_error={client_error}
updateSection={function(e){self.updateSection("");e.preventDefault();}}
@@ -1000,10 +1048,14 @@ var GeneralTab = React.createClass({
);
} else {
+ var minMessage = "Click Edit to upload an image.";
+ if (user.last_picture_update) {
+ minMessage = "Image last updated " + utils.displayDate(user.last_picture_update)
+ }
pictureSection = (
<SettingItemMin
title="Profile Picture"
- describe="Picture inside."
+ describe={minMessage}
updateSection={function(){self.updateSection("picture");}}
/>
);
@@ -1021,6 +1073,8 @@ var GeneralTab = React.createClass({
<div className="divider-light"/>
{usernameSection}
<div className="divider-light"/>
+ {nicknameSection}
+ <div className="divider-light"/>
{emailSection}
<div className="divider-light"/>
{pictureSection}
diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx
index 38f439946..2274f3f2e 100644
--- a/web/react/components/view_image.jsx
+++ b/web/react/components/view_image.jsx
@@ -25,7 +25,11 @@ module.exports = React.createClass({
this.setState({ imgId: nextProps.startId });
},
loadImage: function(id) {
- if (this.state.loaded[id] || this.state.images[id]) return;
+ var imgHeight = $(window).height()-100;
+ if (this.state.loaded[id] || this.state.images[id]){
+ $('.modal .modal-image .image-wrapper img').css("max-height",imgHeight);
+ return;
+ };
var src = "";
if (this.props.imgCount > 0) {
@@ -48,6 +52,7 @@ module.exports = React.createClass({
var loaded = self.state.loaded;
loaded[imgid] = true;
self.setState({ loaded: loaded });
+ $(self.refs.image.getDOMNode()).css("max-height",imgHeight);
};
}(id);
var images = this.state.images;
@@ -56,10 +61,8 @@ module.exports = React.createClass({
},
componentDidUpdate: function() {
if (this.refs.image) {
- var height = $(window).height()-100;
if (this.state.loaded[this.state.imgId]) {
$(this.refs.imageWrap.getDOMNode()).removeClass("default");
- $(this.refs.image.getDOMNode()).css("max-height",height);
}
}
},
diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx
index 3aa985863..f70d60e3a 100644
--- a/web/react/pages/channel.jsx
+++ b/web/react/pages/channel.jsx
@@ -61,17 +61,17 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann
);
React.render(
- <Navbar teamName={team_name} />,
+ <Navbar teamDisplayName={team_name} />,
document.getElementById('navbar')
);
React.render(
- <Sidebar teamName={team_name} teamType={team_type} />,
+ <Sidebar teamDisplayName={team_name} teamType={team_type} />,
document.getElementById('sidebar-left')
);
React.render(
- <RenameTeamModal teamName={team_name} />,
+ <RenameTeamModal teamDisplayName={team_name} />,
document.getElementById('rename_team_modal')
);
@@ -91,7 +91,7 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann
);
React.render(
- <TeamMembersModal teamName={team_name} />,
+ <TeamMembersModal teamDisplayName={team_name} />,
document.getElementById('team_members_modal')
);
@@ -186,7 +186,7 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann
);
React.render(
- <SidebarRightMenu teamName={team_name} teamType={team_type} />,
+ <SidebarRightMenu teamDisplayName={team_name} teamType={team_type} />,
document.getElementById('sidebar-menu')
);
diff --git a/web/react/pages/home.jsx b/web/react/pages/home.jsx
index 08dd32f73..b12fa4949 100644
--- a/web/react/pages/home.jsx
+++ b/web/react/pages/home.jsx
@@ -2,13 +2,14 @@
// See License.txt for license information.
var ChannelStore = require('../stores/channel_store.jsx');
+var TeamStore = require('../stores/team_store.jsx');
var Constants = require('../utils/constants.jsx');
-global.window.setup_home_page = function() {
+global.window.setup_home_page = function(teamURL) {
var last = ChannelStore.getLastVisitedName();
if (last == null || last.length === 0) {
- window.location.replace("/channels/" + Constants.DEFAULT_CHANNEL);
+ window.location = teamURL + "/channels/" + Constants.DEFAULT_CHANNEL;
} else {
- window.location.replace("/channels/" + last);
+ window.location = teamURL + "/channels/" + last;
}
}
diff --git a/web/react/pages/login.jsx b/web/react/pages/login.jsx
index a4e6b438e..8348f0b5d 100644
--- a/web/react/pages/login.jsx
+++ b/web/react/pages/login.jsx
@@ -3,9 +3,9 @@
var Login = require('../components/login.jsx');
-global.window.setup_login_page = function() {
+global.window.setup_login_page = function(teamDisplayName, teamName) {
React.render(
- <Login />,
+ <Login teamDisplayName={teamDisplayName} teamName={teamName}/>,
document.getElementById('login')
);
};
diff --git a/web/react/pages/password_reset.jsx b/web/react/pages/password_reset.jsx
index 6d0d88a10..c7a208973 100644
--- a/web/react/pages/password_reset.jsx
+++ b/web/react/pages/password_reset.jsx
@@ -3,13 +3,13 @@
var PasswordReset = require('../components/password_reset.jsx');
-global.window.setup_password_reset_page = function(is_reset, team_name, domain, hash, data) {
+global.window.setup_password_reset_page = function(is_reset, team_display_name, team_name, hash, data) {
React.render(
<PasswordReset
isReset={is_reset}
+ teamDisplayName={team_display_name}
teamName={team_name}
- domain={domain}
hash={hash}
data={data}
/>,
diff --git a/web/react/pages/signup_team_complete.jsx b/web/react/pages/signup_team_complete.jsx
index c17cbdfac..346f2ab5a 100644
--- a/web/react/pages/signup_team_complete.jsx
+++ b/web/react/pages/signup_team_complete.jsx
@@ -5,7 +5,7 @@ var SignupTeamComplete =require('../components/signup_team_complete.jsx');
global.window.setup_signup_team_complete_page = function(email, name, data, hash) {
React.render(
- <SignupTeamComplete name={name} email={email} hash={hash} data={data} />,
+ <SignupTeamComplete name={name} email={email} hash={hash} data={data}/>,
document.getElementById('signup-team-complete')
);
-}; \ No newline at end of file
+};
diff --git a/web/react/stores/browser_store.jsx b/web/react/stores/browser_store.jsx
index 82cf9a942..4eed754cc 100644
--- a/web/react/stores/browser_store.jsx
+++ b/web/react/stores/browser_store.jsx
@@ -1,85 +1,104 @@
// 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;
+var UserStore;
+function getPrefix() {
+ if (!UserStore) UserStore = require('./user_store.jsx');
+ return UserStore.getCurrentId() + '_';
}
-module.exports.setItem = function(name, value) {
- if (!_initialized) _initialize();
- var user_id = UserStore.getCurrentId();
- localStorage.setItem(user_id + "_" + name, value);
-};
+// Also change model/utils.go ETAG_ROOT_VERSION
+var BROWSER_STORE_VERSION = '.4';
-module.exports.getItem = function(name) {
- if (!_initialized) _initialize();
- var user_id = UserStore.getCurrentId();
- return localStorage.getItem(user_id + "_" + name);
-};
+module.exports = {
+ _initialized: false,
-module.exports.removeItem = function(name) {
- if (!_initialized) _initialize();
- var user_id = UserStore.getCurrentId();
- localStorage.removeItem(user_id + "_" + name);
-};
+ _initialize: function() {
+ var currentVersion = localStorage.getItem("local_storage_version");
+ if (currentVersion !== BROWSER_STORE_VERSION) {
+ this.clear();
+ localStorage.setItem("local_storage_version", BROWSER_STORE_VERSION);
+ }
+ this._initialized = true;
+ },
-module.exports.setGlobalItem = function(name, value) {
- if (!_initialized) _initialize();
- localStorage.setItem(name, value);
-};
+ getItem: function(name, defaultValue) {
+ return this.getGlobalItem(getPrefix() + name, defaultValue);
+ },
-module.exports.getGlobalItem = function(name) {
- if (!_initialized) _initialize();
- return localStorage.getItem(name);
-};
+ setItem: function(name, value) {
+ this.setGlobalItem(getPrefix() + name, value);
+ },
-module.exports.removeGlobalItem = function(name) {
- if (!_initialized) _initialize();
- localStorage.removeItem(name);
-};
+ removeItem: function(name) {
+ if (!this._initialized) this._initialize();
-module.exports.clear = function() {
- localStorage.clear();
- sessionStorage.clear();
-};
+ localStorage.removeItem(getPrefix() + name);
+ },
+
+ setGlobalItem: function(name, value) {
+ if (!this._initialized) this._initialize();
+
+ localStorage.setItem(name, JSON.stringify(value));
+ },
+
+ getGlobalItem: function(name, defaultValue) {
+ if (!this._initialized) this._initialize();
-// 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));
+ var result = null;
+ try {
+ result = JSON.parse(localStorage.getItem(name));
+ } catch (err) {}
+
+ if (result === null && typeof defaultValue !== 'undefined') {
+ result = defaultValue;
}
- }
-};
-module.exports.isLocalStorageSupported = function() {
- try {
- sessionStorage.setItem("testSession", '1');
- sessionStorage.removeItem("testSession");
+ return result;
+ },
- localStorage.setItem("testLocal", '1');
- localStorage.removeItem("testLocal", '1');
+ removeGlobalItem: function(name) {
+ if (!this._initialized) this._initialize();
- return true;
- }
- catch (e) {
- return false;
+ localStorage.removeItem(name);
+ },
+
+ clear: function() {
+ localStorage.clear();
+ sessionStorage.clear();
+ },
+
+ /**
+ * Preforms the given action on each item that has the given prefix
+ * Signiture for action is action(key, value)
+ */
+ actionOnItemsWithPrefix: function (prefix, action) {
+ if (!this._initialized) this._initialize();
+
+ var globalPrefix = getPrefix();
+ var globalPrefixiLen = globalPrefix.length;
+ for (var key in localStorage) {
+ if (key.lastIndexOf(globalPrefix + prefix, 0) === 0) {
+ var userkey = key.substring(globalPrefixiLen);
+ action(userkey, this.getGlobalItem(key));
+ }
+ }
+ },
+
+ isLocalStorageSupported: function() {
+ try {
+ sessionStorage.setItem("testSession", '1');
+ sessionStorage.removeItem("testSession");
+
+ localStorage.setItem("testLocal", '1');
+ if (localStorage.getItem("testLocal") != '1') {
+ return false;
+ }
+ localStorage.removeItem("testLocal", '1');
+
+ return true;
+ } catch (e) {
+ return false;
+ }
}
};
diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx
index 4429a5312..a97f13391 100644
--- a/web/react/stores/channel_store.jsx
+++ b/web/react/stores/channel_store.jsx
@@ -16,6 +16,7 @@ var MORE_CHANGE_EVENT = 'change';
var EXTRA_INFO_EVENT = 'extra_info';
var ChannelStore = assign({}, EventEmitter.prototype, {
+ _current_id: null,
emitChange: function() {
this.emit(CHANGE_EVENT);
},
@@ -43,40 +44,24 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
removeExtraInfoChangeListener: function(callback) {
this.removeListener(EXTRA_INFO_EVENT, callback);
},
- get: function(id) {
- var current = null;
- var c = this._getChannels();
-
- c.some(function(channel) {
- if (channel.id == id) {
- current = channel;
- return true;
+ findFirstBy: function(field, value) {
+ var channels = this._getChannels();
+ for (var i = 0; i < channels.length; i++) {
+ if (channels[i][field] == value) {
+ return channels[i];
}
- return false;
- });
+ }
- return current;
+ return null;
+ },
+ get: function(id) {
+ return this.findFirstBy('id', id);
},
getMember: function(id) {
- var current = null;
return this.getAllMembers()[id];
},
getByName: function(name) {
- var current = null;
- var c = this._getChannels();
-
- c.some(function(channel) {
- if (channel.name == name) {
- current = channel;
- return true;
- }
-
- return false;
-
- });
-
- return current;
-
+ return this.findFirstBy('name', name);
},
getAll: function() {
return this._getChannels();
@@ -88,10 +73,7 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
return this._getMoreChannels();
},
setCurrentId: function(id) {
- if (id == null)
- BrowserStore.removeItem("current_channel_id");
- else
- BrowserStore.setItem("current_channel_id", id);
+ this._current_id = id;
},
setLastVisitedName: function(name) {
if (name == null)
@@ -117,12 +99,12 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
this._storeChannelMembers(cm);
},
getCurrentId: function() {
- return BrowserStore.getItem("current_channel_id");
+ return this._current_id;
},
getCurrent: function() {
- var currentId = ChannelStore.getCurrentId();
+ var currentId = this.getCurrentId();
- if (currentId != null)
+ if (currentId)
return this.get(currentId);
else
return null;
@@ -130,7 +112,7 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
getCurrentMember: function() {
var currentId = ChannelStore.getCurrentId();
- if (currentId != null)
+ if (currentId)
return this.getAllMembers()[currentId];
else
return null;
@@ -145,7 +127,7 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
var currentId = ChannelStore.getCurrentId();
var extra = null;
- if (currentId != null)
+ if (currentId)
extra = this._getExtraInfos()[currentId];
if (extra == null)
@@ -156,7 +138,7 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
getExtraInfo: function(channel_id) {
var extra = null;
- if (channel_id != null)
+ if (channel_id)
extra = this._getExtraInfos()[channel_id];
if (extra == null)
@@ -165,49 +147,22 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
return extra;
},
_storeChannels: function(channels) {
- BrowserStore.setItem("channels", JSON.stringify(channels));
+ BrowserStore.setItem("channels", channels);
},
_getChannels: function() {
- var channels = [];
- try {
- channels = JSON.parse(BrowserStore.getItem("channels"));
- }
- catch (err) {
- }
-
- if (channels == null) {
- channels = [];
- }
-
- return channels;
+ return BrowserStore.getItem("channels", []);
},
_storeChannelMembers: function(channelMembers) {
- BrowserStore.setItem("channel_members", JSON.stringify(channelMembers));
+ BrowserStore.setItem("channel_members", channelMembers);
},
_getChannelMembers: function() {
- var members = {};
- try {
- members = JSON.parse(BrowserStore.getItem("channel_members"));
- }
- catch (err) {
- }
-
- if (members == null) {
- members = {};
- }
-
- return members;
+ return BrowserStore.getItem("channel_members", {});
},
_storeMoreChannels: function(channels) {
- BrowserStore.setItem("more_channels", JSON.stringify(channels));
+ BrowserStore.setItem("more_channels", channels);
},
_getMoreChannels: function() {
- var channels = null;
- try {
- channels = JSON.parse(BrowserStore.getItem("more_channels"));
- }
- catch (err) {
- }
+ var channels = BrowserStore.getItem("more_channels");
if (channels == null) {
channels = {};
@@ -217,22 +172,14 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
return channels;
},
_storeExtraInfos: function(extraInfos) {
- BrowserStore.setItem("extra_infos", JSON.stringify(extraInfos));
+ BrowserStore.setItem("extra_infos", extraInfos);
},
_getExtraInfos: function() {
- var members = {};
- try {
- members = JSON.parse(BrowserStore.getItem("extra_infos"));
- }
- catch (err) {
- }
-
- if (members == null) {
- members = {};
- }
-
- return members;
- }
+ return BrowserStore.getItem("extra_infos", {});
+ },
+ isDefault: function(channel) {
+ return channel.name == Constants.DEFAULT_CHANNEL;
+ }
});
ChannelStore.dispatchToken = AppDispatcher.register(function(payload) {
@@ -271,4 +218,4 @@ ChannelStore.dispatchToken = AppDispatcher.register(function(payload) {
}
});
-module.exports = ChannelStore;
+module.exports = ChannelStore; \ No newline at end of file
diff --git a/web/react/stores/error_store.jsx b/web/react/stores/error_store.jsx
index 3aed6aef2..203b692ec 100644
--- a/web/react/stores/error_store.jsx
+++ b/web/react/stores/error_store.jsx
@@ -29,18 +29,11 @@ var ErrorStore = assign({}, EventEmitter.prototype, {
BrowserStore.removeItem("last_error");
},
getLastError: function() {
- var error = null;
- try {
- error = JSON.parse(BrowserStore.getItem("last_error"));
- }
- catch (err) {
- }
-
- return error;
+ return BrowserStore.getItem('last_error');
},
_storeLastError: function(error) {
- BrowserStore.setItem("last_error", JSON.stringify(error));
+ BrowserStore.setItem("last_error", error);
},
});
diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx
index 8bf3fdcb2..5280bfe08 100644
--- a/web/react/stores/post_store.jsx
+++ b/web/react/stores/post_store.jsx
@@ -106,55 +106,27 @@ var PostStore = assign({}, EventEmitter.prototype, {
this.emitChange();
},
_storePosts: function(channelId, posts) {
- BrowserStore.setItem("posts_" + channelId, JSON.stringify(posts));
+ BrowserStore.setItem("posts_" + channelId, posts);
},
getPosts: function(channelId) {
- var posts = null;
- try {
- posts = JSON.parse(BrowserStore.getItem("posts_" + channelId));
- }
- catch (err) {
- }
-
- return posts;
+ return BrowserStore.getItem("posts_" + channelId);
},
storeSearchResults: function(results, is_mention_search) {
- BrowserStore.setItem("search_results", JSON.stringify(results));
+ BrowserStore.setItem("search_results", results);
is_mention_search = is_mention_search ? true : false; // force to bool
- BrowserStore.setItem("is_mention_search", JSON.stringify(is_mention_search));
+ BrowserStore.setItem("is_mention_search", is_mention_search);
},
getSearchResults: function() {
- var results = null;
- try {
- results = JSON.parse(BrowserStore.getItem("search_results"));
- }
- catch (err) {
- }
-
- return results;
+ return BrowserStore.getItem("search_results");
},
getIsMentionSearch: function() {
- var result = false;
- try {
- result = JSON.parse(BrowserStore.getItem("is_mention_search"));
- }
- catch (err) {
- }
-
- return result;
+ return BrowserStore.getItem("is_mention_search");
},
storeSelectedPost: function(post_list) {
- BrowserStore.setItem("select_post", JSON.stringify(post_list));
+ BrowserStore.setItem("select_post", post_list);
},
getSelectedPost: function() {
- var post_list = null;
- try {
- post_list = JSON.parse(BrowserStore.getItem("select_post"));
- }
- catch (err) {
- }
-
- return post_list;
+ return BrowserStore.getItem("select_post");
},
storeSearchTerm: function(term) {
BrowserStore.setItem("search_term", term);
@@ -165,27 +137,26 @@ var PostStore = assign({}, EventEmitter.prototype, {
storeCurrentDraft: function(draft) {
var channel_id = ChannelStore.getCurrentId();
var user_id = UserStore.getCurrentId();
- BrowserStore.setItem("draft_" + channel_id + "_" + user_id, JSON.stringify(draft));
+ BrowserStore.setItem("draft_" + channel_id + "_" + user_id, draft);
},
getCurrentDraft: function() {
var channel_id = ChannelStore.getCurrentId();
var user_id = UserStore.getCurrentId();
- return JSON.parse(BrowserStore.getItem("draft_" + channel_id + "_" + user_id));
+ return BrowserStore.getItem("draft_" + channel_id + "_" + user_id);
},
storeDraft: function(channel_id, user_id, draft) {
- BrowserStore.setItem("draft_" + channel_id + "_" + user_id, JSON.stringify(draft));
+ BrowserStore.setItem("draft_" + channel_id + "_" + user_id, draft);
},
getDraft: function(channel_id, user_id) {
- return JSON.parse(BrowserStore.getItem("draft_" + channel_id + "_" + user_id));
+ return BrowserStore.getItem("draft_" + channel_id + "_" + user_id);
},
clearDraftUploads: function() {
- BrowserStore.actionOnItemsWithPrefix("draft_", function (key, value) {
- var d = JSON.parse(value);
- if (d) {
- d['uploadsInProgress'] = 0;
- BrowserStore.setItem(key, JSON.stringify(d));
- }
- });
+ BrowserStore.actionOnItemsWithPrefix("draft_", function (key, value) {
+ if (value) {
+ value.uploadsInProgress = 0;
+ BrowserStore.setItem(key, value);
+ }
+ });
}
});
diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx
index 39800ead5..8ebb854c9 100644
--- a/web/react/stores/socket_store.jsx
+++ b/web/react/stores/socket_store.jsx
@@ -10,8 +10,6 @@ var client = require('../utils/client.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
-var BrowserStore = require('../stores/browser_store.jsx');
-
var CHANGE_EVENT = 'change';
var conn;
diff --git a/web/react/stores/team_store.jsx b/web/react/stores/team_store.jsx
index c494cb5b5..3f12725f8 100644
--- a/web/react/stores/team_store.jsx
+++ b/web/react/stores/team_store.jsx
@@ -57,28 +57,19 @@ var TeamStore = assign({}, EventEmitter.prototype, {
else
return null;
},
+ getCurrentTeamUrl: function() {
+ return window.location.origin + "/" + this.getCurrent().name;
+ },
storeTeam: function(team) {
- var teams = this._getTeams();
- teams[team.id] = team;
- this._storeTeams(teams);
+ var teams = this._getTeams();
+ teams[team.id] = team;
+ this._storeTeams(teams);
},
_storeTeams: function(teams) {
- BrowserStore.setItem("user_teams", JSON.stringify(teams));
+ BrowserStore.setItem("user_teams", teams);
},
_getTeams: function() {
- var teams = {};
-
- try {
- teams = JSON.parse(BrowserStore.getItem("user_teams"));
- }
- catch (err) {
- }
-
- if (teams == null) {
- teams = {};
- }
-
- return teams;
+ return BrowserStore.getItem("user_teams", {});
}
});
diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx
index e832b34c7..d03016c5d 100644
--- a/web/react/stores/user_store.jsx
+++ b/web/react/stores/user_store.jsx
@@ -8,7 +8,7 @@ var client = require('../utils/client.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
-var BrowserStore = require('../stores/browser_store.jsx');
+var BrowserStore = require('./browser_store.jsx');
var CHANGE_EVENT = 'change';
var CHANGE_EVENT_SESSIONS = 'change_sessions';
@@ -18,6 +18,8 @@ var CHANGE_EVENT_STATUSES = 'change_statuses';
var UserStore = assign({}, EventEmitter.prototype, {
+ _current_id: null,
+
emitChange: function(userId) {
this.emit(CHANGE_EVENT, userId);
},
@@ -64,13 +66,19 @@ var UserStore = assign({}, EventEmitter.prototype, {
this.removeListener(CHANGE_EVENT_STATUSES, callback);
},
setCurrentId: function(id) {
- if (id == null)
- BrowserStore.removeGlobalItem("current_user_id");
- else
- BrowserStore.setGlobalItem("current_user_id", id);
+ this._current_id = id;
+ if (id == null) {
+ BrowserStore.removeGlobalItem("current_user_id");
+ } else {
+ BrowserStore.setGlobalItem("current_user_id", id);
+ }
},
getCurrentId: function(skipFetch) {
- var current_id = BrowserStore.getGlobalItem("current_user_id");
+ var current_id = this._current_id;
+
+ if (current_id == null) {
+ current_id = BrowserStore.getGlobalItem("current_user_id");
+ }
// this is a speical case to force fetch the
// current user if it's missing
@@ -93,25 +101,11 @@ var UserStore = assign({}, EventEmitter.prototype, {
return this._getProfiles()[this.getCurrentId()];
},
setCurrentUser: function(user) {
- this.saveProfile(user);
this.setCurrentId(user.id);
- },
- getLastDomain: function() {
- var last_domain = BrowserStore.getItem("last_domain");
- if (last_domain == null) {
- last_domain = "";
- }
- return last_domain;
- },
- setLastDomain: function(domain) {
- BrowserStore.setItem("last_domain", domain);
+ this.saveProfile(user);
},
getLastEmail: function() {
- var last_email = BrowserStore.getItem("last_email");
- if (last_email == null) {
- last_email = "";
- }
- return last_email;
+ return BrowserStore.getItem("last_email", '');
},
setLastEmail: function(email) {
BrowserStore.setItem("last_email", email);
@@ -153,116 +147,51 @@ var UserStore = assign({}, EventEmitter.prototype, {
this._storeProfiles(ps);
},
_storeProfiles: function(profiles) {
- BrowserStore.setGlobalItem("profiles", JSON.stringify(profiles));
+ BrowserStore.setItem("profiles", profiles);
var profileUsernameMap = {};
for (var id in profiles) {
profileUsernameMap[profiles[id].username] = profiles[id];
}
- BrowserStore.setGlobalItem("profileUsernameMap", JSON.stringify(profileUsernameMap));
+ BrowserStore.setItem("profileUsernameMap", profileUsernameMap);
},
_getProfiles: function() {
- var profiles = {};
- try {
- profiles = JSON.parse(BrowserStore.getGlobalItem("profiles"));
- }
- catch (err) {
- }
-
- if (profiles == null) {
- profiles = {};
- }
-
- return profiles;
+ return BrowserStore.getItem("profiles", {});
},
_getProfilesUsernameMap: function() {
- var profileUsernameMap = {};
- try {
- profileUsernameMap = JSON.parse(BrowserStore.getGlobalItem("profileUsernameMap"));
- }
- catch (err) {
- }
-
- if (profileUsernameMap == null) {
- profileUsernameMap = {};
- }
-
- return profileUsernameMap;
+ return BrowserStore.getItem("profileUsernameMap", {});
},
setSessions: function(sessions) {
- BrowserStore.setItem("sessions", JSON.stringify(sessions));
+ BrowserStore.setItem("sessions", sessions);
},
getSessions: function() {
- var sessions = [];
- try {
- sessions = JSON.parse(BrowserStore.getItem("sessions"));
- }
- catch (err) {
- }
- if (sessions == null) {
- sessions = [];
- }
-
- return sessions;
+ return BrowserStore.getItem("sessions", []);
},
setAudits: function(audits) {
- BrowserStore.setItem("audits", JSON.stringify(audits));
+ BrowserStore.setItem("audits", audits);
},
getAudits: function() {
- var audits = [];
- try {
- audits = JSON.parse(BrowserStore.getItem("audits"));
- }
- catch (err) {
- }
-
- if (audits == null) {
- audits = [];
- }
-
- return audits;
+ return BrowserStore.getItem("audits", []);
},
setTeams: function(teams) {
- BrowserStore.setItem("teams", JSON.stringify(teams));
+ BrowserStore.setItem("teams", teams);
},
getTeams: function() {
- var teams = [];
- try {
- teams = JSON.parse(BrowserStore.getItem("teams"));
-
- }
- catch (err) {
- }
-
- if (teams == null) {
- teams = [];
- }
-
- return teams;
+ return BrowserStore.getItem("teams", []);
},
getCurrentMentionKeys: function() {
var user = this.getCurrentUser();
- if (user.notify_props && user.notify_props.mention_keys) {
- var keys = user.notify_props.mention_keys.split(',');
- if (user.full_name.length > 0 && user.notify_props.first_name === "true") {
- var first = user.full_name.split(' ')[0];
- if (first.length > 0) keys.push(first);
- }
+ var keys = [];
- if (user.notify_props.all === "true") keys.push('@all');
- if (user.notify_props.channel === "true") keys.push('@channel');
+ if (user.notify_props && user.notify_props.mention_keys) keys = keys.concat(user.notify_props.mention_keys.split(','));
+ if (user.first_name && user.notify_props.first_name === "true") keys.push(user.first_name);
+ if (user.notify_props.all === "true") keys.push('@all');
+ if (user.notify_props.channel === "true") keys.push('@channel');
- return keys;
- } else {
- return [];
- }
+ return keys;
},
getLastVersion: function() {
- var last_version = BrowserStore.getItem("last_version");
- if (last_version == null) {
- last_version = "";
- }
- return last_version;
+ return BrowserStore.getItem("last_version", '');
},
setLastVersion: function(version) {
BrowserStore.setItem("last_version", version);
@@ -272,7 +201,7 @@ var UserStore = assign({}, EventEmitter.prototype, {
this.emitStatusesChange();
},
_setStatuses: function(statuses) {
- BrowserStore.setItem("statuses", JSON.stringify(statuses));
+ BrowserStore.setItem("statuses", statuses);
},
setStatus: function(user_id, status) {
var statuses = this.getStatuses();
@@ -281,18 +210,7 @@ var UserStore = assign({}, EventEmitter.prototype, {
this.emitStatusesChange();
},
getStatuses: function() {
- var statuses = {};
- try {
- statuses = JSON.parse(BrowserStore.getItem("statuses"));
- }
- catch (err) {
- }
-
- if (statuses == null) {
- statuses = {};
- }
-
- return statuses;
+ return BrowserStore.getItem("statuses", {});
},
getStatus: function(id) {
return this.getStatuses()[id];
@@ -341,4 +259,3 @@ UserStore.setMaxListeners(0);
global.window.UserStore = UserStore;
module.exports = UserStore;
-
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 11d4c2601..1c31dc5ed 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -2,6 +2,7 @@
// See License.txt for license information.
var BrowserStore = require('../stores/browser_store.jsx');
+var TeamStore = require('../stores/team_store.jsx');
module.exports.track = function(category, action, label, prop, val) {
global.window.snowplow('trackStructEvent', category, action, label, prop, val);
@@ -44,7 +45,12 @@ function handleError(method_name, xhr, status, err) {
module.exports.track('api', 'api_weberror', method_name, 'message', msg);
if (xhr.status == 401) {
- window.location.href = '/login?redirect=' + encodeURIComponent(window.location.pathname+window.location.search);
+ if (window.location.href.indexOf("/channels") === 0) {
+ window.location.pathname = '/login?redirect=' + encodeURIComponent(window.location.pathname+window.location.search);
+ } else {
+ var teamURL = window.location.href.split('/channels')[0];
+ window.location.href = teamURL + '/login?redirect=' + encodeURIComponent(window.location.pathname+window.location.search);
+ }
}
return e;
@@ -205,17 +211,18 @@ module.exports.resetPassword = function(data, success, error) {
module.exports.logout = function() {
module.exports.track('api', 'api_users_logout');
- BrowserStore.clear();
- window.location.href = "/logout";
+ var currentTeamUrl = TeamStore.getCurrentTeamUrl();
+ BrowserStore.clear();
+ window.location.href = currentTeamUrl + "/logout";
};
-module.exports.loginByEmail = function(domain, email, password, success, error) {
+module.exports.loginByEmail = function(name, email, password, success, error) {
$.ajax({
url: "/api/v1/users/login",
dataType: 'json',
contentType: 'application/json',
type: 'POST',
- data: JSON.stringify({domain: domain, email: email, password: password}),
+ data: JSON.stringify({name: name, email: email, password: password}),
success: function(data, textStatus, xhr) {
module.exports.track('api', 'api_users_login_success', data.team_id, 'email', data.email);
success(data, textStatus, xhr);
@@ -317,7 +324,7 @@ module.exports.inviteMembers = function(data, success, error) {
module.exports.track('api', 'api_teams_invite_members');
};
-module.exports.updateTeamName = function(data, success, error) {
+module.exports.updateTeamDisplayName = function(data, success, error) {
$.ajax({
url: "/api/v1/teams/update_name",
dataType: 'json',
@@ -326,7 +333,7 @@ module.exports.updateTeamName = function(data, success, error) {
data: JSON.stringify(data),
success: success,
error: function(xhr, status, err) {
- e = handleError("updateTeamName", xhr, status, err);
+ e = handleError("updateTeamDisplayName", xhr, status, err);
error(e);
}
});
@@ -334,13 +341,13 @@ module.exports.updateTeamName = function(data, success, error) {
module.exports.track('api', 'api_teams_update_name');
};
-module.exports.signupTeam = function(email, name, success, error) {
+module.exports.signupTeam = function(email, display_name, success, error) {
$.ajax({
url: "/api/v1/teams/signup",
dataType: 'json',
contentType: 'application/json',
type: 'POST',
- data: JSON.stringify({email: email, name: name}),
+ data: JSON.stringify({email: email, display_name: display_name}),
success: success,
error: function(xhr, status, err) {
e = handleError("singupTeam", xhr, status, err);
@@ -366,16 +373,16 @@ module.exports.createTeam = function(team, success, error) {
});
};
-module.exports.findTeamByDomain = function(domain, success, error) {
+module.exports.findTeamByName = function(teamName, success, error) {
$.ajax({
- url: "/api/v1/teams/find_team_by_domain",
+ url: "/api/v1/teams/find_team_by_name",
dataType: 'json',
contentType: 'application/json',
type: 'POST',
- data: JSON.stringify({domain: domain}),
+ data: JSON.stringify({name: teamName}),
success: success,
error: function(xhr, status, err) {
- e = handleError("findTeamByDomain", xhr, status, err);
+ e = handleError("findTeamByName", xhr, status, err);
error(e);
}
});
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 3aadfb4b0..187e3c4a3 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -54,7 +54,7 @@ module.exports = {
DEFAULT_CHANNEL: 'town-square',
OFFTOPIC_CHANNEL: 'off-topic',
POST_CHUNK_SIZE: 60,
- RESERVED_DOMAINS: [
+ RESERVED_TEAM_NAMES: [
"www",
"web",
"admin",
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 5ded0e76f..00580af6e 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -16,10 +16,10 @@ module.exports.isEmail = function(email) {
};
module.exports.cleanUpUrlable = function(input) {
- var cleaned = input.trim().replace(/-/g, ' ').replace(/[^\w\s]/gi, '').toLowerCase().replace(/\s/g, '-');
- cleaned = cleaned.replace(/^\-+/, '');
- cleaned = cleaned.replace(/\-+$/, '');
- return cleaned;
+ var cleaned = input.trim().replace(/-/g, ' ').replace(/[^\w\s]/gi, '').toLowerCase().replace(/\s/g, '-');
+ cleaned = cleaned.replace(/^\-+/, '');
+ cleaned = cleaned.replace(/\-+$/, '');
+ return cleaned;
};
@@ -114,7 +114,7 @@ module.exports.notifyMe = function(title, body, channel) {
if (channel) {
module.exports.switchChannel(channel);
} else {
- window.location.href = "/channels/town-square";
+ window.location.href = "/";
}
};
setTimeout(function(){
@@ -198,7 +198,13 @@ module.exports.getTimestamp = function() {
}
var testUrlMatch = function(text) {
- var urlMatcher = new Autolinker.matchParser.MatchParser;
+ var urlMatcher = new Autolinker.matchParser.MatchParser({
+ urls: true,
+ emails: false,
+ twitter: false,
+ phone: false,
+ hashtag: false,
+ });
var result = [];
var replaceFn = function(match) {
var linkData = {};
@@ -225,7 +231,7 @@ module.exports.extractLinks = function(text) {
}
return { "links": links, "text": text };
-}
+}
module.exports.escapeRegExp = function(string) {
return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
@@ -303,18 +309,25 @@ var getYoutubeEmbed = function(link) {
};
var success = function(data) {
- $('.video-uploader.'+youtubeId).html(data.data.uploader);
- $('.video-title.'+youtubeId).find('a').html(data.data.title);
+ if(!data.items.length || !data.items[0].snippet) {
+ return;
+ }
+ var metadata = data.items[0].snippet;
+ $('.video-uploader.'+youtubeId).html(metadata.channelTitle);
+ $('.video-title.'+youtubeId).find('a').html(metadata.title);
$(".post-list-holder-by-time").scrollTop($(".post-list-holder-by-time")[0].scrollHeight);
$(".post-list-holder-by-time").perfectScrollbar('update');
};
- $.ajax({
- async: true,
- url: 'https://gdata.youtube.com/feeds/api/videos/'+youtubeId+'?v=2&alt=jsonc',
- type: 'GET',
- success: success
- });
+ if(config.GoogleDeveloperKey) {
+ $.ajax({
+ async: true,
+ url: "https://www.googleapis.com/youtube/v3/videos",
+ type: 'GET',
+ data: {part:"snippet", id:youtubeId, key:config.GoogleDeveloperKey},
+ success: success
+ });
+ }
return (
<div className="post-comment">
@@ -671,13 +684,13 @@ module.exports.isValidUsername = function (name) {
error = "First character must be a letter.";
}
- else
+ else
{
var lowerName = name.toLowerCase().trim();
- for (var i = 0; i < Constants.RESERVED_USERNAMES.length; i++)
+ for (var i = 0; i < Constants.RESERVED_USERNAMES.length; i++)
{
- if (lowerName === Constants.RESERVED_USERNAMES[i])
+ if (lowerName === Constants.RESERVED_USERNAMES[i])
{
error = "Cannot use a reserved word as a username.";
break;
@@ -695,8 +708,8 @@ module.exports.switchChannel = function(channel, teammate_name) {
id: channel.id
});
- var domain = window.location.href.split('/channels')[0];
- history.replaceState('data', '', domain + '/channels/' + channel.name);
+ var teamURL = window.location.href.split('/channels')[0];
+ history.replaceState('data', '', teamURL + '/channels/' + channel.name);
if (channel.type === 'D' && teammate_name) {
document.title = teammate_name + " " + document.title.substring(document.title.lastIndexOf("-"));
@@ -771,14 +784,57 @@ Image.prototype.load = function(url, progressCallback) {
Image.prototype.completedPercentage = 0;
-module.exports.getHomeLink = function() {
- if (config.HomeLink != "") {
- return config.HomeLink;
- }
- var parts = window.location.host.split(".");
- if (parts.length <= 1) {
- return window.location.protocol + "//" + window.location.host;
- }
- parts[0] = "www";
- return window.location.protocol + "//" + parts.join(".");
-}
+module.exports.changeColor =function(col, amt) {
+
+ var usePound = false;
+
+ if (col[0] == "#") {
+ col = col.slice(1);
+ usePound = true;
+ }
+
+ var num = parseInt(col,16);
+
+ var r = (num >> 16) + amt;
+
+ if (r > 255) r = 255;
+ else if (r < 0) r = 0;
+
+ var b = ((num >> 8) & 0x00FF) + amt;
+
+ if (b > 255) b = 255;
+ else if (b < 0) b = 0;
+
+ var g = (num & 0x0000FF) + amt;
+
+ if (g > 255) g = 255;
+ else if (g < 0) g = 0;
+
+ return (usePound?"#":"") + String("000000" + (g | (b << 8) | (r << 16)).toString(16)).slice(-6);
+};
+
+module.exports.getFullName = function(user) {
+ if (user.first_name && user.last_name) {
+ return user.first_name + " " + user.last_name;
+ } else if (user.first_name) {
+ return user.first_name;
+ } else if (user.last_name) {
+ return user.last_name;
+ } else {
+ return "";
+ }
+};
+
+module.exports.getDisplayName = function(user) {
+ if (user.nickname && user.nickname.trim().length > 0) {
+ return user.nickname;
+ } else {
+ var fullName = module.exports.getFullName(user);
+
+ if (fullName) {
+ return fullName;
+ } else {
+ return user.username;
+ }
+ }
+};
diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss
index fd6225bdd..1fb970075 100644
--- a/web/sass-files/sass/partials/_base.scss
+++ b/web/sass-files/sass/partials/_base.scss
@@ -10,6 +10,9 @@ body {
height: 100%;
&.white {
background: #fff;
+ > .container-fluid {
+ overflow: auto;
+ }
.inner__wrap {
> .row.content {
min-height: 100%;
@@ -53,6 +56,9 @@ div.theme {
.form-control {
@include border-radius(2px);
+ &.no-resize {
+ resize: none;
+ }
}
.form-group {
@@ -126,6 +132,10 @@ div.theme {
to { transform: scale(1) rotate(360deg);}
}
+.glyphicon-refresh-animate {
+ @include animation(spin .7s infinite linear);
+}
+
.black-bg {
background-color: black !important;
}
diff --git a/web/sass-files/sass/partials/_files.scss b/web/sass-files/sass/partials/_files.scss
index 79142176e..56d03e171 100644
--- a/web/sass-files/sass/partials/_files.scss
+++ b/web/sass-files/sass/partials/_files.scss
@@ -32,6 +32,7 @@
}
}
.preview-img {
+ display: block;
height: auto;
max-width: 100%;
}
@@ -137,7 +138,7 @@
border: 1px solid #E2E2E2;
background-color: #FFF;
background-repeat: no-repeat;
- background-position: left center;
+ background-position: top left;
}
a {
text-decoration: none;
diff --git a/web/sass-files/sass/partials/_headers.scss b/web/sass-files/sass/partials/_headers.scss
index 1ec1109a5..adeaa70d7 100644
--- a/web/sass-files/sass/partials/_headers.scss
+++ b/web/sass-files/sass/partials/_headers.scss
@@ -75,14 +75,16 @@
// Team Header in Sidebar
.sidebar--left, .sidebar--menu {
.team__header {
- padding: 0 15px 0 15px;
+ padding: 10px;
@include legacy-pie-clearfix;
a {
color: #fff;
}
.navbar-right {
font-size: 0.85em;
- margin: 16px -5px 0;
+ position: absolute;
+ top: 20px;
+ right: 22px;
.dropdown-toggle {
padding: 0 10px;
}
@@ -93,24 +95,38 @@
}
}
.dropdown__icon {
- background: url("../images/dropdown-icon.png");
- width: 4px;
- height: 16px;
- @include background-size(100% 100%);
- display: inline-block;
+ fill: #fff;
}
}
- .team__name {
+ .settings__link a:hover, a:visited, a:link, a:active {
+ text-decoration: none;
+ }
+ .user__picture {
+ width: 36px;
+ height: 36px;
float: left;
- line-height: 50px;
+ @include border-radius(36px);
+ }
+ .header__info {
+ padding-left: 42px;
+ color: #fff;
+ }
+ .team__name, .user__name {
+ display: block;
+ line-height: 18px;
font-weight: 600;
- font-size: 1.2em;
+ font-size: 16px;
max-width: 80%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-decoration: none;
}
+ .user__name {
+ font-size: 14px;
+ font-weight: 400;
+ color: #eee;
+ }
> .nav {
> li {
> a {
@@ -144,9 +160,10 @@
font-size: 14px;
line-height: 50px;
#member_popover {
+ margin-right: 5px;
width: 45px;
color: #999;
-
+ cursor: pointer;
}
&.alt {
margin: 0;
@@ -213,3 +230,25 @@
top: 1px;
}
}
+
+.channel-header__links {
+ height: 32px;
+ vertical-align: top;
+ display: inline-block;
+ width: 15px;
+ margin: 9px 4px 3px 0;
+ &:hover {
+ svg {
+ fill: #888;
+ }
+ }
+ a {
+ height: 100%;
+ display: block;
+ }
+ svg {
+ vertical-align: top;
+ margin-top: 8px;
+ fill: #AAA;
+ }
+}
diff --git a/web/sass-files/sass/partials/_loading.scss b/web/sass-files/sass/partials/_loading.scss
index bc819e8f5..d71055722 100644
--- a/web/sass-files/sass/partials/_loading.scss
+++ b/web/sass-files/sass/partials/_loading.scss
@@ -2,13 +2,8 @@
display: table;
width: 100%;
height: 100%;
- position: absolute;
- @include box-sizing(border-box);
+ padding: 60px;
text-align: center;
- &.loading-screen--channel {
- position: relative;
- padding: 4em 0 3.5em;
- }
.loading__content {
display: table-cell;
vertical-align: middle;
@@ -19,11 +14,7 @@
margin: 0 0.2em 0;
display: inline-block;
}
- }
-}
-.loading-screen {
- .loading__content {
.round {
background-color: #444;
width: 4px;
@@ -32,43 +23,18 @@
margin: 0 2px;
opacity: 0.1;
@include border-radius(10px);
- -moz-animation: move 0.75s infinite linear;
- -webkit-animation: move 0.75s infinite linear;
- }
-
- #round_1 {
- -moz-animation-delay: .2s;
- -webkit-animation-delay: .2s;
+ @include animation(move 0.75s infinite linear);
}
- #round_2 {
- -moz-animation-delay: .4s;
- -webkit-animation-delay: .4s;
- }
-
- #round_3 {
- -moz-animation-delay: .6s;
- -webkit-animation-delay: .6s;
- }
-
- @-moz-keyframes move {
- 0% {
- opacity: 1;
+ @for $i from 1 through 3 {
+ .round-#{$i} {
+ @include animation-delay(.2s*$i);
}
-
- 100% {
- opacity: 0.1;
- };
}
- @-webkit-keyframes move {
- 0% {
- opacity: 1;
- }
-
- 100% {
- opacity: 0.1;
- };
+ @include keyframes(move) {
+ from { opacity: 1; }
+ to { opacity: 0.1; }
}
}
}
diff --git a/web/sass-files/sass/partials/_mentions.scss b/web/sass-files/sass/partials/_mentions.scss
index da46866c8..1396f21a1 100644
--- a/web/sass-files/sass/partials/_mentions.scss
+++ b/web/sass-files/sass/partials/_mentions.scss
@@ -11,13 +11,14 @@
position: absolute;
z-index: 1060;
.mentions-box {
- max-height: 303px;
- position:absolute;
- background-color:#fff;
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ background-color: #fff;
border: $border-gray;
overflow-x: hidden;
overflow-y: scroll;
- bottom:0;
+ bottom: 0;
}
}
@@ -36,6 +37,10 @@
}
}
+.mentions-focus {
+ background-color: #E6F2FA;
+}
+
.mentions-text {
font-color:black;
}
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index 707e71cf0..f359037c5 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -154,7 +154,8 @@
background: #FFF;
position: relative;
max-width: 90%;
- min-width: 280px;
+ min-height: 50px;
+ min-width: 320px;
@include border-radius(3px);
display: table;
margin: 0 auto;
diff --git a/web/sass-files/sass/partials/_navbar.scss b/web/sass-files/sass/partials/_navbar.scss
index 62864afb7..6d8f11ce3 100644
--- a/web/sass-files/sass/partials/_navbar.scss
+++ b/web/sass-files/sass/partials/_navbar.scss
@@ -43,6 +43,7 @@
font-size: 16px;
.heading {
margin-right: 3px;
+ font-weight: 600;
color: #fff;
}
.header-dropdown__icon {
diff --git a/web/sass-files/sass/partials/_popover.scss b/web/sass-files/sass/partials/_popover.scss
new file mode 100644
index 000000000..fa1b44841
--- /dev/null
+++ b/web/sass-files/sass/partials/_popover.scss
@@ -0,0 +1,9 @@
+.user-popover {
+ cursor: pointer;
+ display: inline-block;
+}
+
+.user-popover__image {
+ margin: 0 0 10px;
+ @include border-radius(128px);
+} \ No newline at end of file
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index d0c536363..481ed63a5 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -119,6 +119,7 @@ body.ios {
table-layout: fixed;
width: 100%;
min-height: 100%;
+ height: 100%;
.post-list__content {
display: table-cell;
vertical-align: bottom;
@@ -186,6 +187,12 @@ body.ios {
.post-create-footer {
@include clearfix;
padding: 0;
+ .has-error {
+ .control-label {
+ font-weight: normal;
+ margin-bottom: 0;
+ }
+ }
.msg-typing {
min-height: 20px;
line-height: 18px;
@@ -207,6 +214,12 @@ body.ios {
.dropdown, .comment-icon__container {
@include opacity(1);
}
+ .dropdown-toggle:after {
+ content: '...';
+ }
+ .dropdown-toggle:hover:after {
+ content: '[...]';
+ }
}
background: #f5f5f5;
}
@@ -424,4 +437,4 @@ body.ios {
width: 40px;
}
}
-}
+} \ No newline at end of file
diff --git a/web/sass-files/sass/partials/_post_right.scss b/web/sass-files/sass/partials/_post_right.scss
index 8816393c8..4cf3e32a1 100644
--- a/web/sass-files/sass/partials/_post_right.scss
+++ b/web/sass-files/sass/partials/_post_right.scss
@@ -11,7 +11,7 @@
.post {
&.post--root {
- padding: 1em 1em 0;
+ padding: 0 1em 0;
margin: 1em 0;
hr {
border-color: #DDD;
@@ -62,6 +62,11 @@
}
}
+.post-right-channel__name {
+ font-weight: 600;
+ margin: 0 0 10px 0;
+}
+
.post-right-root-container li {
display: inline;
list-style-type: none;
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index 9c0c09ee3..a33d69378 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -413,7 +413,7 @@
}
}
.footer, .footer-pane, .footer-push {
- height: 185px;
+ height: auto;
}
.footer-pane {
.footer-link {
@@ -432,14 +432,16 @@
color: #fff;
.search__form {
border: none;
- padding: 0 10px 0 30px;
+ padding: 0 60px 0 25px;
.form-control {
+ line-height: 31px;
background: none;
color: #fff;
- border-bottom: 1px solid #fff;
- border-bottom: 1px solid rgba(#fff, 0.7);
border-radius: 0;
- padding: 0 0 0 23px;
+ padding: 0 10px 0;
+ @include input-placeholder {
+ color: rgba(#fff, 0.6);
+ }
}
::-webkit-input-placeholder {
color: #fff;
@@ -534,6 +536,11 @@
.sidebar--right__close {
display: none;
}
+ .search__form {
+ .glyphicon {
+ color: #fff;
+ }
+ }
}
.inner__wrap {
&.move--right {
@@ -570,6 +577,8 @@
.modal {
.modal-image {
.image-wrapper {
+ font-size: 12px;
+ max-width: 280px;
.modal-close {
@include opacity(1);
}
diff --git a/web/sass-files/sass/partials/_search.scss b/web/sass-files/sass/partials/_search.scss
index 8d51d00c0..d4a4da243 100644
--- a/web/sass-files/sass/partials/_search.scss
+++ b/web/sass-files/sass/partials/_search.scss
@@ -2,21 +2,20 @@
padding: 8px 8px 8px 0;
}
.sidebar__collapse {
- width: 20px;
- height: 30px;
+ width: auto;
+ height: auto;
position: absolute;
- top: 10px;
- left: 6px;
+ top: 17px;
+ right: 15px;
cursor: pointer;
- background: url("../images/arrow-left.png") center no-repeat;
- @include background-size(10px 15px);
z-index: 5;
display: none;
}
.sidebar__search-icon {
position: absolute;
- left: 40px;
+ left: 15px;
top: 18px;
+ font-size: 16px;
@include opacity(0.8);
display: none;
}
diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss
index af759c650..e60bc290e 100644
--- a/web/sass-files/sass/partials/_settings.scss
+++ b/web/sass-files/sass/partials/_settings.scss
@@ -1,6 +1,10 @@
.user-settings {
background: #fff;
min-height:300px;
+ .table-responsive {
+ max-width: 560px;
+ max-height: 300px;
+ }
}
.settings-modal {
diff --git a/web/sass-files/sass/partials/_sidebar--right.scss b/web/sass-files/sass/partials/_sidebar--right.scss
index a0e82fd2f..d02a92448 100644
--- a/web/sass-files/sass/partials/_sidebar--right.scss
+++ b/web/sass-files/sass/partials/_sidebar--right.scss
@@ -10,6 +10,14 @@
&.move--left {
right: 0;
}
+ .sidebar--right__back {
+ color: #666;
+ width: 20px;
+ text-align: center;
+ margin: 0 0 0 -6px;
+ font-size: 12px;
+ display: inline-block;
+ }
.sidebar-right__body {
border-left: $border-gray;
border-top: $border-gray;
diff --git a/web/sass-files/sass/partials/_signup.scss b/web/sass-files/sass/partials/_signup.scss
index a714aa44f..98931279b 100644
--- a/web/sass-files/sass/partials/_signup.scss
+++ b/web/sass-files/sass/partials/_signup.scss
@@ -6,7 +6,7 @@
}
.signup-team__container {
padding: 100px 0px 50px 0px;
- max-width: 340px;
+ max-width: 600px;
margin: 0 auto;
font-size: 1.1em;
position: relative;
@@ -118,4 +118,4 @@
.signup-team-login {
padding-bottom: 10px;
font-weight: 700;
-} \ No newline at end of file
+}
diff --git a/web/sass-files/sass/partials/_variables.scss b/web/sass-files/sass/partials/_variables.scss
index eb1f3eef3..5d883ab44 100644
--- a/web/sass-files/sass/partials/_variables.scss
+++ b/web/sass-files/sass/partials/_variables.scss
@@ -7,10 +7,4 @@ $primary-color: #2389D7;
$primary-color--hover: darken(#2389D7, 5%);
$body-bg: #e9e9e9;
$header-bg: #f9f9f9;
-$border-gray: 1px solid #ddd;
-
-// Animation
-.glyphicon-refresh-animate {
- -animation: spin .7s infinite linear;
- -webkit-animation: spin2 .7s infinite linear;
-} \ No newline at end of file
+$border-gray: 1px solid #ddd; \ No newline at end of file
diff --git a/web/sass-files/sass/styles.scss b/web/sass-files/sass/styles.scss
index 9cc26320c..294f6122a 100644
--- a/web/sass-files/sass/styles.scss
+++ b/web/sass-files/sass/styles.scss
@@ -15,6 +15,7 @@
@import "partials/headers";
@import "partials/footer";
@import "partials/content";
+@import "partials/popover";
@import "partials/post";
@import "partials/post_right";
@import "partials/navbar";
@@ -29,7 +30,7 @@
@import "partials/modal";
@import "partials/mentions";
@import "partials/error";
-@import "partials/loading";
+@import "partials/loading";
// Responsive Css
@import "partials/responsive";
diff --git a/web/static/config/config.js b/web/static/config/config.js
index 45c713da2..0d564b77e 100644
--- a/web/static/config/config.js
+++ b/web/static/config/config.js
@@ -16,6 +16,10 @@ var config = {
RequireInviteNames: false,
AllowSignupDomainsWizard: false,
+ // Google Developer Key (for Youtube API links)
+ // Leave blank to disable
+ GoogleDeveloperKey: "",
+
// Privacy switches
ShowEmail: true,
diff --git a/web/static/images/dropdown-icon.png b/web/static/images/dropdown-icon.png
deleted file mode 100644
index 5c271cfc7..000000000
--- a/web/static/images/dropdown-icon.png
+++ /dev/null
Binary files differ
diff --git a/web/templates/channel.html b/web/templates/channel.html
index d96aee3d4..eaf0f2563 100644
--- a/web/templates/channel.html
+++ b/web/templates/channel.html
@@ -46,7 +46,7 @@
<div id="direct_channel_modal"></div>
<div id="channel_info_modal"></div>
<script>
-window.setup_channel_page('{{ .Props.TeamName }}', '{{ .Props.TeamType }}', '{{ .Props.TeamId }}', '{{ .Props.ChannelName }}', '{{ .Props.ChannelId }}');
+window.setup_channel_page('{{ .Props.TeamDisplayName }}', '{{ .Props.TeamType }}', '{{ .Props.TeamId }}', '{{ .Props.ChannelName }}', '{{ .Props.ChannelId }}');
</script>
</body>
</html>
diff --git a/web/templates/head.html b/web/templates/head.html
index ead648571..d14340998 100644
--- a/web/templates/head.html
+++ b/web/templates/head.html
@@ -31,7 +31,6 @@
<link rel="stylesheet" href="/static/css/styles.css">
<script src="/static/js/perfect-scrollbar-0.6.3.jquery.js"></script>
- <script src="/static/js/bundle.js"></script>
<script type="text/javascript" src="https://www.google.com/jsapi?autoload={'modules':[{'name':'visualization','version':'1','packages':['annotationchart']}]}"></script>
@@ -43,6 +42,9 @@
}
config.SiteName = '{{ .SiteName }}';
</script>
+
+ <script src="/static/js/bundle.js"></script>
+
<script>
if (config.LogglyWriteKey != null && config.LogglyWriteKey !== "") {
var _LTracker = _LTracker || [];
@@ -60,7 +62,7 @@
var user = window.UserStore.getCurrentUser(true);
if (user) {
analytics.identify(user.id, {
- name: user.full_name,
+ name: user.nickname,
email: user.email,
createdAt: user.create_at,
username: user.username,
diff --git a/web/templates/home.html b/web/templates/home.html
index abf8062f2..9ec8b7000 100644
--- a/web/templates/home.html
+++ b/web/templates/home.html
@@ -17,7 +17,7 @@
</div>
</div>
<script>
- window.setup_home_page();
+ window.setup_home_page({{.Props.TeamURL}});
</script>
</body>
</html>
diff --git a/web/templates/login.html b/web/templates/login.html
index c107e1ad5..24cebec8f 100644
--- a/web/templates/login.html
+++ b/web/templates/login.html
@@ -7,7 +7,7 @@
<div class="inner__wrap">
<div class="row content">
<div class="signup-header">
- {{.Props.TeamName}}
+ {{.Props.TeamDisplayName}}
</div>
<div class="col-sm-12">
<div id="login"></div>
@@ -20,7 +20,7 @@
</div>
</div>
<script>
- window.setup_login_page();
+window.setup_login_page({{.Props.TeamDisplayName}}, {{.Props.TeamName}});
</script>
</body>
</html>
diff --git a/web/templates/password_reset.html b/web/templates/password_reset.html
index 8b63556b1..6244f6418 100644
--- a/web/templates/password_reset.html
+++ b/web/templates/password_reset.html
@@ -9,7 +9,7 @@
</div>
</div>
<script>
- window.setup_password_reset_page('{{ .Props.IsReset }}', '{{ .Props.TeamName }}', '{{ .Props.Domain }}', '{{ .Props.Hash }}', '{{ .Props.Data }}');
+ window.setup_password_reset_page('{{ .Props.IsReset }}', '{{ .Props.TeamDisplayName }}', '{{ .Props.TeamName }}', '{{ .Props.Hash }}', '{{ .Props.Data }}');
</script>
</body>
</html>
diff --git a/web/templates/signup_team.html b/web/templates/signup_team.html
index fad332bee..b86590589 100644
--- a/web/templates/signup_team.html
+++ b/web/templates/signup_team.html
@@ -12,7 +12,6 @@
<h2>All team communication in one place, searchable and accessible anywhere</h2>
<h4 class="text--light">{{ .SiteName }} is free for an unlimited time, for unlimited users </h4 class="text--light">
<div id="signup-team"></div>
- <a class="signup-team-login" href="/login">or Sign In</a>
</div>
</div>
<div class="footer-push"></div>
@@ -23,7 +22,7 @@
</div>
</div>
<script>
- window.setup_signup_team_page();
+window.setup_signup_team_page();
</script>
</body>
</html>
diff --git a/web/templates/signup_team_complete.html b/web/templates/signup_team_complete.html
index 59f49cdbd..674e54ee2 100644
--- a/web/templates/signup_team_complete.html
+++ b/web/templates/signup_team_complete.html
@@ -19,7 +19,7 @@
</div>
</div>
<script>
- window.setup_signup_team_complete_page('{{.Props.Email}}', '{{.Props.Name}}', '{{.Props.Data}}', '{{.Props.Hash}}');
+window.setup_signup_team_complete_page('{{.Props.Email}}', '{{.Props.DisplayName}}', '{{.Props.Data}}', '{{.Props.Hash}}');
</script>
</body>
</html>
diff --git a/web/templates/signup_team_confirm.html b/web/templates/signup_team_confirm.html
index 9e21126da..3a6134af3 100644
--- a/web/templates/signup_team_confirm.html
+++ b/web/templates/signup_team_confirm.html
@@ -8,7 +8,6 @@
<div class="row content">
<div class="col-sm-12">
<div class="signup-team__container">
- <p>Did you mean to sign-in rather than sign up? Sign in <a href="/login">here</a>. </p>
<h3>Sign up Complete</h3>
<p>Please check your email: {{ .Props.Email }}<br>
You email contains a link to set up your team</p>
diff --git a/web/templates/signup_user_complete.html b/web/templates/signup_user_complete.html
index 5fe907ba7..0cc655b63 100644
--- a/web/templates/signup_user_complete.html
+++ b/web/templates/signup_user_complete.html
@@ -19,7 +19,7 @@
</div>
</div>
<script>
- window.setup_signup_user_complete_page('{{.Props.Email}}', '{{.Props.TeamDomain}}', '{{.Props.TeamName}}', '{{.Props.TeamId}}', '{{.Props.Data}}', '{{.Props.Hash}}');
+ window.setup_signup_user_complete_page('{{.Props.Email}}', '{{.Props.TeamName}}', '{{.Props.TeamDisplayName}}', '{{.Props.TeamId}}', '{{.Props.Data}}', '{{.Props.Hash}}');
</script>
</body>
</html>
diff --git a/web/web.go b/web/web.go
index e771157d8..3e4bc2d53 100644
--- a/web/web.go
+++ b/web/web.go
@@ -42,25 +42,28 @@ func (me *HtmlTemplatePage) Render(c *api.Context, w http.ResponseWriter) {
func InitWeb() {
l4g.Debug("Initializing web routes")
+ mainrouter := api.Srv.Router
+
staticDir := utils.FindDir("web/static")
l4g.Debug("Using static directory at %v", staticDir)
- api.Srv.Router.PathPrefix("/static/").Handler(http.StripPrefix("/static/",
- http.FileServer(http.Dir(staticDir))))
-
- api.Srv.Router.Handle("/", api.AppHandler(root)).Methods("GET")
- api.Srv.Router.Handle("/login", api.AppHandler(login)).Methods("GET")
- api.Srv.Router.Handle("/signup_team_confirm/", api.AppHandler(signupTeamConfirm)).Methods("GET")
- api.Srv.Router.Handle("/signup_team_complete/", api.AppHandler(signupTeamComplete)).Methods("GET")
- api.Srv.Router.Handle("/signup_user_complete/", api.AppHandler(signupUserComplete)).Methods("GET")
-
- api.Srv.Router.Handle("/logout", api.AppHandler(logout)).Methods("GET")
-
- api.Srv.Router.Handle("/verify", api.AppHandler(verifyEmail)).Methods("GET")
- api.Srv.Router.Handle("/find_team", api.AppHandler(findTeam)).Methods("GET")
- api.Srv.Router.Handle("/reset_password", api.AppHandler(resetPassword)).Methods("GET")
-
- csr := api.Srv.Router.PathPrefix("/channels").Subrouter()
- csr.Handle("/{name:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}", api.UserRequired(getChannel)).Methods("GET")
+ mainrouter.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir))))
+
+ mainrouter.Handle("/", api.AppHandlerIndependent(root)).Methods("GET")
+ mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}", api.AppHandler(login)).Methods("GET")
+ mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/", api.AppHandler(login)).Methods("GET")
+ mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/login", api.AppHandler(login)).Methods("GET")
+ mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/logout", api.AppHandler(logout)).Methods("GET")
+ mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/reset_password", api.AppHandler(resetPassword)).Methods("GET")
+ // Bug in gorilla.mux pervents us from using regex here.
+ mainrouter.Handle("/{team}/channels/{channelname}", api.UserRequired(getChannel)).Methods("GET")
+
+ // Anything added here must have an _ in it so it does not conflict with team names
+ mainrouter.Handle("/signup_team_complete/", api.AppHandlerIndependent(signupTeamComplete)).Methods("GET")
+ mainrouter.Handle("/signup_user_complete/", api.AppHandlerIndependent(signupUserComplete)).Methods("GET")
+ mainrouter.Handle("/signup_team_confirm/", api.AppHandlerIndependent(signupTeamConfirm)).Methods("GET")
+ mainrouter.Handle("/verify_email", api.AppHandlerIndependent(verifyEmail)).Methods("GET")
+ mainrouter.Handle("/find_team", api.AppHandlerIndependent(findTeam)).Methods("GET")
+ mainrouter.Handle("/signup_team", api.AppHandlerIndependent(signup)).Methods("GET")
watchAndParseTemplates()
}
@@ -128,46 +131,53 @@ func root(c *api.Context, w http.ResponseWriter, r *http.Request) {
}
if len(c.Session.UserId) == 0 {
- if api.IsTestDomain(r) || strings.Index(r.Host, "www") == 0 || strings.Index(r.Host, "beta") == 0 || strings.Index(r.Host, "ci") == 0 {
- page := NewHtmlTemplatePage("signup_team", "Signup")
- page.Render(c, w)
- } else {
- login(c, w, r)
- }
+ page := NewHtmlTemplatePage("signup_team", "Signup")
+ page.Render(c, w)
} else {
page := NewHtmlTemplatePage("home", "Home")
+ page.Props["TeamURL"] = c.GetTeamURL()
page.Render(c, w)
}
}
-func login(c *api.Context, w http.ResponseWriter, r *http.Request) {
+func signup(c *api.Context, w http.ResponseWriter, r *http.Request) {
+
if !CheckBrowserCompatability(c, r) {
return
}
- teamName := "Beta"
- teamDomain := ""
- siteDomain := "." + utils.Cfg.ServiceSettings.Domain
+ page := NewHtmlTemplatePage("signup_team", "Signup")
+ page.Render(c, w)
+}
+
+func login(c *api.Context, w http.ResponseWriter, r *http.Request) {
+ if !CheckBrowserCompatability(c, r) {
+ return
+ }
+ params := mux.Vars(r)
+ teamName := params["team"]
- if utils.Cfg.ServiceSettings.Mode == utils.MODE_DEV {
- teamDomain = "developer"
- } else if utils.Cfg.ServiceSettings.Mode == utils.MODE_BETA {
- teamDomain = "beta"
+ var team *model.Team
+ if tResult := <-api.Srv.Store.Team().GetByName(teamName); tResult.Err != nil {
+ l4g.Error("Couldn't find team name=%v, teamURL=%v, err=%v", teamName, c.GetTeamURL(), tResult.Err.Message)
+ // This should probably do somthing nicer
+ http.Redirect(w, r, "http://"+r.Host, http.StatusTemporaryRedirect)
+ return
} else {
- teamDomain, siteDomain = model.GetSubDomain(c.TeamUrl)
- siteDomain = "." + siteDomain + ".com"
+ team = tResult.Data.(*model.Team)
+ }
- if tResult := <-api.Srv.Store.Team().GetByDomain(teamDomain); tResult.Err != nil {
- l4g.Error("Couldn't find team teamDomain=%v, siteDomain=%v, teamUrl=%v, err=%v", teamDomain, siteDomain, c.TeamUrl, tResult.Err.Message)
- } else {
- teamName = tResult.Data.(*model.Team).Name
- }
+ // If we are already logged into this team then go to home
+ if len(c.Session.UserId) != 0 && c.Session.TeamId == team.Id {
+ page := NewHtmlTemplatePage("home", "Home")
+ page.Props["TeamURL"] = c.GetTeamURL()
+ page.Render(c, w)
+ return
}
page := NewHtmlTemplatePage("login", "Login")
+ page.Props["TeamDisplayName"] = team.DisplayName
page.Props["TeamName"] = teamName
- page.Props["TeamDomain"] = teamDomain
- page.Props["SiteDomain"] = siteDomain
page.Render(c, w)
}
@@ -198,7 +208,7 @@ func signupTeamComplete(c *api.Context, w http.ResponseWriter, r *http.Request)
page := NewHtmlTemplatePage("signup_team_complete", "Complete Team Sign Up")
page.Props["Email"] = props["email"]
- page.Props["Name"] = props["name"]
+ page.Props["DisplayName"] = props["display_name"]
page.Props["Data"] = data
page.Props["Hash"] = hash
page.Render(c, w)
@@ -225,8 +235,8 @@ func signupUserComplete(c *api.Context, w http.ResponseWriter, r *http.Request)
}
props["email"] = ""
+ props["display_name"] = team.DisplayName
props["name"] = team.Name
- props["domain"] = team.Domain
props["id"] = team.Id
data = model.MapToJson(props)
hash = ""
@@ -249,8 +259,8 @@ func signupUserComplete(c *api.Context, w http.ResponseWriter, r *http.Request)
page := NewHtmlTemplatePage("signup_user_complete", "Complete User Sign Up")
page.Props["Email"] = props["email"]
+ page.Props["TeamDisplayName"] = props["display_name"]
page.Props["TeamName"] = props["name"]
- page.Props["TeamDomain"] = props["domain"]
page.Props["TeamId"] = props["id"]
page.Props["Data"] = data
page.Props["Hash"] = hash
@@ -259,12 +269,12 @@ func signupUserComplete(c *api.Context, w http.ResponseWriter, r *http.Request)
func logout(c *api.Context, w http.ResponseWriter, r *http.Request) {
api.Logout(c, w, r)
- http.Redirect(w, r, "/", http.StatusFound)
+ http.Redirect(w, r, c.GetTeamURL(), http.StatusFound)
}
func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
- name := params["name"]
+ name := params["channelname"]
var channelId string
if result := <-api.Srv.Store.Channel().CheckPermissionsToByName(c.Session.TeamId, name, c.Session.UserId); result.Err != nil {
@@ -303,8 +313,8 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) {
//api.Handle404(w, r)
//Bad channel urls just redirect to the town-square for now
-
- http.Redirect(w,r,"/channels/town-square", http.StatusFound)
+
+ http.Redirect(w, r, c.GetTeamURL()+"/channels/town-square", http.StatusFound)
return
}
}
@@ -319,8 +329,8 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) {
}
page := NewHtmlTemplatePage("channel", "")
- page.Title = name + " - " + team.Name + " " + page.SiteName
- page.Props["TeamName"] = team.Name
+ page.Title = name + " - " + team.DisplayName + " " + page.SiteName
+ page.Props["TeamDisplayName"] = team.DisplayName
page.Props["TeamType"] = team.Type
page.Props["TeamId"] = team.Id
page.Props["ChannelName"] = name
@@ -331,7 +341,7 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) {
func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) {
resend := r.URL.Query().Get("resend")
- domain := r.URL.Query().Get("domain")
+ name := r.URL.Query().Get("name")
email := r.URL.Query().Get("email")
hashedId := r.URL.Query().Get("hid")
userId := r.URL.Query().Get("uid")
@@ -339,7 +349,7 @@ func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) {
if resend == "true" {
teamId := ""
- if result := <-api.Srv.Store.Team().GetByDomain(domain); result.Err != nil {
+ if result := <-api.Srv.Store.Team().GetByName(name); result.Err != nil {
c.Err = result.Err
return
} else {
@@ -351,7 +361,7 @@ func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) {
return
} else {
user := result.Data.(*model.User)
- api.FireAndForgetVerifyEmail(user.Id, strings.Split(user.FullName, " ")[0], user.Email, domain, c.TeamUrl)
+ api.FireAndForgetVerifyEmail(user.Id, strings.Split(user.Nickname, " ")[0], user.Email, name, c.GetTeamURL())
http.Redirect(w, r, "/", http.StatusFound)
return
}
@@ -387,6 +397,8 @@ func resetPassword(c *api.Context, w http.ResponseWriter, r *http.Request) {
isResetLink := true
hash := r.URL.Query().Get("h")
data := r.URL.Query().Get("d")
+ params := mux.Vars(r)
+ teamName := params["team"]
if len(hash) == 0 || len(data) == 0 {
isResetLink = false
@@ -405,30 +417,25 @@ func resetPassword(c *api.Context, w http.ResponseWriter, r *http.Request) {
}
}
- teamName := "Developer/Beta"
- domain := ""
- if utils.Cfg.ServiceSettings.Mode != utils.MODE_DEV {
- domain, _ = model.GetSubDomain(c.TeamUrl)
-
- var team *model.Team
- if tResult := <-api.Srv.Store.Team().GetByDomain(domain); tResult.Err != nil {
- c.Err = tResult.Err
- return
- } else {
- team = tResult.Data.(*model.Team)
- }
+ teamDisplayName := "Developer/Beta"
+ var team *model.Team
+ if tResult := <-api.Srv.Store.Team().GetByName(teamName); tResult.Err != nil {
+ c.Err = tResult.Err
+ return
+ } else {
+ team = tResult.Data.(*model.Team)
+ }
- if team != nil {
- teamName = team.Name
- }
+ if team != nil {
+ teamDisplayName = team.DisplayName
}
page := NewHtmlTemplatePage("password_reset", "")
page.Title = "Reset Password - " + page.SiteName
- page.Props["TeamName"] = teamName
+ page.Props["TeamDisplayName"] = teamDisplayName
page.Props["Hash"] = hash
page.Props["Data"] = data
- page.Props["Domain"] = domain
+ page.Props["TeamName"] = teamName
page.Props["IsReset"] = strconv.FormatBool(isResetLink)
page.Render(c, w)
}