summaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
author=Corey Hulen <corey@hulen.com>2015-07-05 09:02:23 -0800
committer=Corey Hulen <corey@hulen.com>2015-07-05 09:02:23 -0800
commit592d68cf4134b668e3ff962d17f8aa75b1bc055f (patch)
tree6e743b07487caf5fe350439aefdf0355a7e80d9b /web
parent72bfc1ee2afd2fa4a34186b62f144f8f6c50d693 (diff)
parent4a3003c0dcf7d642f233493e32b07beed5d08327 (diff)
downloadchat-592d68cf4134b668e3ff962d17f8aa75b1bc055f.tar.gz
chat-592d68cf4134b668e3ff962d17f8aa75b1bc055f.tar.bz2
chat-592d68cf4134b668e3ff962d17f8aa75b1bc055f.zip
Merge branch 'master' into mm-1391
Diffstat (limited to 'web')
-rw-r--r--web/react/components/channel_header.jsx41
-rw-r--r--web/react/components/channel_info_modal.jsx15
-rw-r--r--web/react/components/channel_notifications.jsx208
-rw-r--r--web/react/components/create_comment.jsx28
-rw-r--r--web/react/components/create_post.jsx34
-rw-r--r--web/react/components/file_preview.jsx2
-rw-r--r--web/react/components/file_upload.jsx18
-rw-r--r--web/react/components/get_link_modal.jsx2
-rw-r--r--web/react/components/login.jsx2
-rw-r--r--web/react/components/mention.jsx10
-rw-r--r--web/react/components/mention_list.jsx20
-rw-r--r--web/react/components/more_direct_channels.jsx2
-rw-r--r--web/react/components/post.jsx18
-rw-r--r--web/react/components/post_body.jsx2
-rw-r--r--web/react/components/post_list.jsx122
-rw-r--r--web/react/components/post_right.jsx20
-rw-r--r--web/react/components/search_results.jsx8
-rw-r--r--web/react/components/setting_item_max.jsx2
-rw-r--r--web/react/components/sidebar.jsx11
-rw-r--r--web/react/components/sidebar_right_menu.jsx1
-rw-r--r--web/react/components/signup_team_complete.jsx6
-rw-r--r--web/react/components/team_settings.jsx9
-rw-r--r--web/react/components/team_settings_modal.jsx2
-rw-r--r--web/react/components/textbox.jsx2
-rw-r--r--web/react/components/user_profile.jsx10
-rw-r--r--web/react/components/user_settings.jsx96
-rw-r--r--web/react/components/user_settings_modal.jsx2
-rw-r--r--web/react/stores/user_store.jsx3
-rw-r--r--web/react/utils/constants.jsx3
-rw-r--r--web/react/utils/utils.jsx41
-rw-r--r--web/sass-files/sass/partials/_base.scss22
-rw-r--r--web/sass-files/sass/partials/_headers.scss4
-rw-r--r--web/sass-files/sass/partials/_loading.scss68
-rw-r--r--web/sass-files/sass/partials/_mentions.scss30
-rw-r--r--web/sass-files/sass/partials/_modal.scss40
-rw-r--r--web/sass-files/sass/partials/_post.scss16
-rw-r--r--web/sass-files/sass/partials/_responsive.scss51
-rw-r--r--web/sass-files/sass/partials/_search.scss2
-rw-r--r--web/sass-files/sass/partials/_settings.scss4
-rw-r--r--web/sass-files/sass/partials/_sidebar--left.scss3
-rw-r--r--web/sass-files/sass/styles.scss1
-rw-r--r--web/templates/channel.html2
-rw-r--r--web/templates/find_team.html1
-rw-r--r--web/templates/head.html2
-rw-r--r--web/templates/home.html1
-rw-r--r--web/templates/login.html1
-rw-r--r--web/templates/password_reset.html1
-rw-r--r--web/templates/signup_team.html1
-rw-r--r--web/templates/signup_team_complete.html1
-rw-r--r--web/templates/signup_team_confirm.html1
-rw-r--r--web/templates/signup_user_complete.html1
-rw-r--r--web/templates/verify.html1
-rw-r--r--web/templates/welcome.html1
-rw-r--r--web/web.go2
54 files changed, 691 insertions, 306 deletions
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index ade58a10a..48cb4d13b 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -15,17 +15,8 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
-function getExtraInfoStateFromStores() {
- return {
- extra_info: ChannelStore.getCurrentExtraInfo()
- };
-}
-
var ExtraMembers = React.createClass({
componentDidMount: function() {
- ChannelStore.addExtraInfoChangeListener(this._onChange);
- ChannelStore.addChangeListener(this._onChange);
-
var originalLeave = $.fn.popover.Constructor.prototype.leave;
$.fn.popover.Constructor.prototype.leave = function(obj) {
var self = obj instanceof this.constructor ? obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type);
@@ -49,27 +40,21 @@ var ExtraMembers = React.createClass({
});
},
- componentWillUnmount: function() {
- ChannelStore.removeExtraInfoChangeListener(this._onChange);
- ChannelStore.removeChangeListener(this._onChange);
- },
- _onChange: function() {
- var newState = getExtraInfoStateFromStores();
- if (!utils.areStatesEqual(newState, this.state)) {
- this.setState(newState);
- }
- },
- getInitialState: function() {
- return getExtraInfoStateFromStores();
- },
render: function() {
- var count = this.state.extra_info.members.length == 0 ? "-" : this.state.extra_info.members.length;
- count = this.state.extra_info.members.length > 19 ? "20+" : count;
+ 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;
- this.state.extra_info.members.forEach(function(m) {
- data_content += "<div style='white-space: nowrap'>" + m.username + "</div>";
- });
+ if(sortedMembers) {
+ sortedMembers.sort(function(a,b) {
+ return a.username.localeCompare(b.username);
+ })
+
+ sortedMembers.forEach(function(m) {
+ data_content += "<div style='white-space: nowrap'>" + m.username + "</div>";
+ });
+ }
return (
<div style={{"cursor" : "pointer"}} id="member_popover" data-toggle="popover" data-content={data_content} data-original-title="Members" >
@@ -228,7 +213,7 @@ module.exports = React.createClass({
<a href="#"><strong className="heading">{channelTitle}</strong></a>
}
</th>
- <th><ExtraMembers channelId={this.state.channel.id} /></th>
+ <th><ExtraMembers members={this.state.users} channelId={this.state.channel.id} /></th>
{ searchForm }
<th>
<div className="dropdown" style={{"marginLeft":"5px", "marginRight":"10px"}}>
diff --git a/web/react/components/channel_info_modal.jsx b/web/react/components/channel_info_modal.jsx
index 191297ce4..18addb52f 100644
--- a/web/react/components/channel_info_modal.jsx
+++ b/web/react/components/channel_info_modal.jsx
@@ -35,9 +35,18 @@ module.exports = React.createClass({
<h4 className="modal-title" id="myModalLabel">{channel.display_name}</h4>
</div>
<div className="modal-body">
- <p><strong>Channel Name: </strong>{channel.display_name}</p>
- <p><strong>Channel Handle: </strong>{channel.name}</p>
- <p><strong>Channel ID: </strong>{channel.id}</p>
+ <div className="row form-group">
+ <div className="col-sm-3 info__label">Channel Name: </div>
+ <div className="col-sm-9">{channel.display_name}</div>
+ </div>
+ <div className="row form-group">
+ <div className="col-sm-3 info__label">Channel Handle:</div>
+ <div className="col-sm-9">{channel.name}</div>
+ </div>
+ <div className="row">
+ <div className="col-sm-3 info__label">Channel ID:</div>
+ <div className="col-sm-9">{channel.id}</div>
+ </div>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
diff --git a/web/react/components/channel_notifications.jsx b/web/react/components/channel_notifications.jsx
index 085536a0a..638d16576 100644
--- a/web/react/components/channel_notifications.jsx
+++ b/web/react/components/channel_notifications.jsx
@@ -1,6 +1,8 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
+var SettingItemMin = require('./setting_item_min.jsx');
+var SettingItemMax = require('./setting_item_max.jsx');
var utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx');
@@ -9,26 +11,50 @@ var ChannelStore = require('../stores/channel_store.jsx');
module.exports = React.createClass({
componentDidMount: function() {
+ ChannelStore.addChangeListener(this._onChange);
+
var self = this;
$(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
var button = e.relatedTarget;
var channel_id = button.dataset.channelid;
var notifyLevel = ChannelStore.getMember(channel_id).notify_level;
- self.setState({ notify_level: notifyLevel, title: button.dataset.title, channel_id: channel_id });
+ var quietMode = false;
+ if (notifyLevel === "quiet") quietMode = true;
+ self.setState({ notify_level: notifyLevel, quiet_mode: quietMode, title: button.dataset.title, channel_id: channel_id });
});
},
+ componentWillUnmount: function() {
+ ChannelStore.removeChangeListener(this._onChange);
+ },
+ _onChange: function() {
+ if (!this.state.channel_id) return;
+ var notifyLevel = ChannelStore.getMember(this.state.channel_id).notify_level;
+ var quietMode = false;
+ if (notifyLevel === "quiet") quietMode = true;
+
+ var newState = this.state;
+ newState.notify_level = notifyLevel;
+ newState.quiet_mode = quietMode;
+
+ if (!utils.areStatesEqual(this.state, newState)) {
+ this.setState(newState);
+ }
+ },
+ updateSection: function(section) {
+ this.setState({ activeSection: section });
+ },
getInitialState: function() {
- return { notify_level: "", title: "", channel_id: "" };
+ return { notify_level: "", title: "", channel_id: "", activeSection: "" };
},
- handleUpdate: function(e) {
+ handleUpdate: function() {
var channel_id = this.state.channel_id;
- var notify_level = this.state.notify_level;
+ var notify_level = this.state.quiet_mode ? "quiet" : this.state.notify_level;
var data = {};
data["channel_id"] = channel_id;
data["user_id"] = UserStore.getCurrentId();
- data["notify_level"] = this.state.notify_level;
+ data["notify_level"] = notify_level;
if (!data["notify_level"] || data["notify_level"].length === 0) return;
@@ -37,7 +63,7 @@ module.exports = React.createClass({
var member = ChannelStore.getMember(channel_id);
member.notify_level = notify_level;
ChannelStore.setChannelMember(member);
- $(this.refs.modal.getDOMNode()).modal('hide');
+ this.updateSection("");
}.bind(this),
function(err) {
this.setState({ server_error: err.message });
@@ -45,42 +71,138 @@ module.exports = React.createClass({
);
},
handleRadioClick: function(notifyLevel) {
- this.setState({ notify_level: notifyLevel });
+ this.setState({ notify_level: notifyLevel, quiet_mode: false });
this.refs.modal.getDOMNode().focus();
},
- handleQuietToggle: function() {
- if (this.state.notify_level === "quiet") {
- this.setState({ notify_level: "none" });
- this.refs.modal.getDOMNode().focus();
- } else {
- this.setState({ notify_level: "quiet" });
- this.refs.modal.getDOMNode().focus();
- }
+ handleQuietToggle: function(quietMode) {
+ this.setState({ notify_level: "none", quiet_mode: quietMode });
+ this.refs.modal.getDOMNode().focus();
},
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 allActive = "";
- var mentionActive = "";
- var noneActive = "";
- var quietActive = "";
- var desktopHidden = "";
-
- if (this.state.notify_level === "quiet") {
- desktopHidden = "hidden";
- quietActive = "active";
- } else if (this.state.notify_level === "mention") {
- mentionActive = "active";
- } else if (this.state.notify_level === "none") {
- noneActive = "active";
+ var self = this;
+
+ var desktopSection;
+ if (this.state.activeSection === 'desktop') {
+ var notifyActive = [false, false, false];
+ if (this.state.notify_level === "mention") {
+ notifyActive[1] = true;
+ } else if (this.state.notify_level === "all") {
+ notifyActive[0] = true;
+ } else {
+ notifyActive[2] = true;
+ }
+
+ var inputs = [];
+
+ inputs.push(
+ <div>
+ <div className="radio">
+ <label>
+ <input type="radio" checked={notifyActive[0]} onClick={function(){self.handleRadioClick("all")}}>For all activity</input>
+ </label>
+ <br/>
+ </div>
+ <div className="radio">
+ <label>
+ <input type="radio" checked={notifyActive[1]} onClick={function(){self.handleRadioClick("mention")}}>Only for mentions</input>
+ </label>
+ <br/>
+ </div>
+ <div className="radio">
+ <label>
+ <input type="radio" checked={notifyActive[2]} onClick={function(){self.handleRadioClick("none")}}>Never</input>
+ </label>
+ </div>
+ </div>
+ );
+
+ desktopSection = (
+ <SettingItemMax
+ title="Send desktop notifications"
+ inputs={inputs}
+ submit={this.handleUpdate}
+ server_error={server_error}
+ updateSection={function(e){self.updateSection("");self._onChange();e.preventDefault();}}
+ />
+ );
+ } else {
+ var describe = "";
+ if (this.state.notify_level === "mention") {
+ describe = "Only for mentions";
+ } else if (this.state.notify_level === "all") {
+ describe = "For all activity";
+ } else {
+ describe = "Never";
+ }
+
+ desktopSection = (
+ <SettingItemMin
+ title="Send desktop notifications"
+ describe={describe}
+ updateSection={function(e){self.updateSection("desktop");e.preventDefault();}}
+ />
+ );
+ }
+
+ var quietSection;
+ if (this.state.activeSection === 'quiet') {
+ var quietActive = ["",""];
+ if (this.state.quiet_mode) {
+ quietActive[0] = "active";
+ } else {
+ quietActive[1] = "active";
+ }
+
+ var inputs = [];
+
+ inputs.push(
+ <div>
+ <div className="btn-group" data-toggle="buttons-radio">
+ <button className={"btn btn-default "+quietActive[0]} onClick={function(){self.handleQuietToggle(true)}}>On</button>
+ <button className={"btn btn-default "+quietActive[1]} onClick={function(){self.handleQuietToggle(false)}}>Off</button>
+ </div>
+ </div>
+ );
+
+ inputs.push(
+ <div>
+ <br/>
+ Enabling quiet mode will turn off desktop notifications and only mark the channel as unread if you have been mentioned.
+ </div>
+ );
+
+ quietSection = (
+ <SettingItemMax
+ title="Quiet mode"
+ inputs={inputs}
+ submit={this.handleUpdate}
+ server_error={server_error}
+ updateSection={function(e){self.updateSection("");self._onChange();e.preventDefault();}}
+ />
+ );
} else {
- allActive = "active";
+ var describe = "";
+ if (this.state.quiet_mode) {
+ describe = "On";
+ } else {
+ describe = "Off";
+ }
+
+ quietSection = (
+ <SettingItemMin
+ title="Quiet mode"
+ describe={describe}
+ updateSection={function(e){self.updateSection("quiet");e.preventDefault();}}
+ />
+ );
}
var self = this;
return (
<div className="modal fade" id="channel_notifications" ref="modal" tabIndex="-1" role="dialog" aria-hidden="true">
- <div className="modal-dialog">
+ <div className="modal-dialog settings-modal">
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" data-dismiss="modal">
@@ -90,31 +212,23 @@ module.exports = React.createClass({
<h4 className="modal-title">{"Notification Preferences for " + this.state.title}</h4>
</div>
<div className="modal-body">
- <div className={desktopHidden}>
- <span>Desktop Notifications</span>
- <br/>
- <div className="btn-group" data-toggle="buttons-radio">
- <button className={"btn btn-default "+allActive} onClick={function(){self.handleRadioClick("all")}}>Any activity (default)</button>
- <button className={"btn btn-default "+mentionActive} onClick={function(){self.handleRadioClick("mention")}}>Mentions of my name</button>
- <button className={"btn btn-default "+noneActive} onClick={function(){self.handleRadioClick("none")}}>Nothing</button>
+ <div className="settings-table">
+ <div className="settings-content">
+ <div ref="wrapper" className="user-settings">
+ <br/>
+ <div className="divider-dark first"/>
+ {desktopSection}
+ <div className="divider-light"/>
+ {quietSection}
+ <div className="divider-dark"/>
</div>
- <br/>
- <br/>
</div>
- <span>Quiet Mode</span>
- <br/>
- <div className="btn-group" data-toggle="buttons-checkbox">
- <button className={"btn btn-default "+quietActive} onClick={this.handleQuietToggle}>Quiet Mode</button>
</div>
{ server_error }
</div>
- <div className="modal-footer">
- <button type="button" className="btn btn-primary" onClick={this.handleUpdate}>Done</button>
- </div>
</div>
</div>
</div>
-
);
}
});
diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx
index cb7aa371c..9e3feb25c 100644
--- a/web/react/components/create_comment.jsx
+++ b/web/react/components/create_comment.jsx
@@ -112,13 +112,28 @@ module.exports = React.createClass({
return { messageText: '', uploadsInProgress: 0, previews: [], submitting: false };
},
setUploads: function(val) {
- var num = this.state.uploadsInProgress + val;
- this.setState({uploadsInProgress: num});
+ var oldInProgress = this.state.uploadsInProgress
+ var newInProgress = oldInProgress + val;
+
+ if (newInProgress + this.state.previews.length > Constants.MAX_UPLOAD_FILES) {
+ newInProgress = Constants.MAX_UPLOAD_FILES - this.state.previews.length;
+ this.setState({limit_error: "Uploads limited to " + Constants.MAX_UPLOAD_FILES + " files maximum. Please use additional comments for more files."});
+ } else {
+ this.setState({limit_error: null});
+ }
+
+ var numToUpload = newInProgress - oldInProgress;
+ if (numToUpload <= 0) return 0;
+
+ this.setState({uploadsInProgress: newInProgress});
+
+ return numToUpload;
},
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 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;
var preview = <div/>;
if (this.state.previews.length > 0 || this.state.uploadsInProgress > 0) {
@@ -129,13 +144,6 @@ module.exports = React.createClass({
uploadsInProgress={this.state.uploadsInProgress} />
);
}
- var limit_previews = ""
- if (this.state.previews.length > 5) {
- limit_previews = <div className='has-error'><label className='control-label'>{ "Note: While all files will be available, only first five will show thumbnails." }</label></div>
- }
- if (this.state.previews.length > 20) {
- limit_previews = <div className='has-error'><label className='control-label'>{ "Note: Uploads limited to 20 files maximum. Please use additional posts for more files." }</label></div>
- }
return (
<form onSubmit={this.handleSubmit}>
@@ -159,7 +167,7 @@ module.exports = React.createClass({
<input type="button" className="btn btn-primary comment-btn pull-right" value="Add Comment" onClick={this.handleSubmit} />
{ post_error }
{ server_error }
- { limit_previews }
+ { limit_error }
</div>
</div>
{ preview }
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index 5a0b6f85f..0c23dcfac 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -51,7 +51,7 @@ module.exports = React.createClass({
false,
function(data) {
PostStore.storeDraft(data.channel_id, user_id, null);
- this.setState({ messageText: '', submitting: false, post_error: null, previews: [], server_error: null });
+ this.setState({ messageText: '', submitting: false, post_error: null, previews: [], server_error: null, limit_error: null });
if (data.goto_location.length > 0) {
window.location.href = data.goto_location;
@@ -71,7 +71,7 @@ module.exports = React.createClass({
client.createPost(post, ChannelStore.getCurrent(),
function(data) {
PostStore.storeDraft(data.channel_id, data.user_id, null);
- this.setState({ messageText: '', submitting: false, post_error: null, previews: [], server_error: null });
+ this.setState({ messageText: '', submitting: false, post_error: null, previews: [], server_error: null, limit_error: null });
this.resizePostHolder();
AsyncClient.getPosts(true);
@@ -207,21 +207,36 @@ module.exports = React.createClass({
return { channel_id: ChannelStore.getCurrentId(), messageText: messageText, uploadsInProgress: 0, previews: previews, submitting: false, initialText: messageText };
},
setUploads: function(val) {
- var num = this.state.uploadsInProgress + val;
+ var oldInProgress = this.state.uploadsInProgress
+ var newInProgress = oldInProgress + val;
+
+ if (newInProgress + this.state.previews.length > Constants.MAX_UPLOAD_FILES) {
+ newInProgress = Constants.MAX_UPLOAD_FILES - this.state.previews.length;
+ this.setState({limit_error: "Uploads limited to " + Constants.MAX_UPLOAD_FILES + " files maximum. Please use additional posts for more files."});
+ } else {
+ this.setState({limit_error: null});
+ }
+
+ var numToUpload = newInProgress - oldInProgress;
+ if (numToUpload <= 0) return 0;
+
var draft = PostStore.getCurrentDraft();
if (!draft) {
draft = {}
draft['message'] = '';
draft['previews'] = [];
}
- draft['uploadsInProgress'] = num;
+ draft['uploadsInProgress'] = newInProgress;
PostStore.storeCurrentDraft(draft);
- this.setState({uploadsInProgress: num});
+ this.setState({uploadsInProgress: newInProgress});
+
+ return numToUpload;
},
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 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;
var preview = <div/>;
if (this.state.previews.length > 0 || this.state.uploadsInProgress > 0) {
@@ -232,13 +247,6 @@ module.exports = React.createClass({
uploadsInProgress={this.state.uploadsInProgress} />
);
}
- var limit_previews = ""
- if (this.state.previews.length > 5) {
- limit_previews = <div className='has-error'><label className='control-label'>{ "Note: While all files will be available, only first five will show thumbnails." }</label></div>
- }
- if (this.state.previews.length > 20) {
- limit_previews = <div className='has-error'><label className='control-label'>{ "Note: Uploads limited to 20 files maximum. Please use additional posts for more files." }</label></div>
- }
return (
<form id="create_post" ref="topDiv" role="form" onSubmit={this.handleSubmit}>
@@ -260,7 +268,7 @@ module.exports = React.createClass({
<div className={post_error ? 'post-create-footer has-error' : 'post-create-footer'}>
{ post_error }
{ server_error }
- { limit_previews }
+ { limit_error }
{ preview }
<MsgTyping channelId={this.state.channel_id} parentId=""/>
</div>
diff --git a/web/react/components/file_preview.jsx b/web/react/components/file_preview.jsx
index 99327c22f..17a1e2bc2 100644
--- a/web/react/components/file_preview.jsx
+++ b/web/react/components/file_preview.jsx
@@ -10,7 +10,7 @@ var Constants = require('../utils/constants.jsx');
module.exports = React.createClass({
handleRemove: function(e) {
var previewDiv = e.target.parentNode.parentNode;
- this.props.onRemove(previewDiv.dataset.filename);
+ this.props.onRemove(previewDiv.getAttribute('data-filename'));
},
render: function() {
var previews = [];
diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx
index c03a61c63..f2429f17e 100644
--- a/web/react/components/file_upload.jsx
+++ b/web/react/components/file_upload.jsx
@@ -12,18 +12,18 @@ module.exports = React.createClass({
this.props.onUploadError(null);
- //This looks redundant, but must be done this way due to
- //setState being an asynchronous call
+ // This looks redundant, but must be done this way due to
+ // setState being an asynchronous call
var numFiles = 0;
- for(var i = 0; i < files.length && i <= 20 ; i++) {
+ for(var i = 0; i < files.length && i < Constants.MAX_UPLOAD_FILES; i++) {
if (files[i].size <= Constants.MAX_FILE_SIZE) {
numFiles++;
}
}
- this.props.setUploads(numFiles);
+ var numToUpload = this.props.setUploads(numFiles);
- for (var i = 0; i < files.length && i <= 20; i++) {
+ for (var i = 0; i < files.length && i < numToUpload; i++) {
if (files[i].size > Constants.MAX_FILE_SIZE) {
this.props.onUploadError("Files must be no more than " + Constants.MAX_FILE_SIZE/1000000 + " MB");
continue;
@@ -70,8 +70,8 @@ module.exports = React.createClass({
self.props.onUploadError(null);
- //This looks redundant, but must be done this way due to
- //setState being an asynchronous call
+ // This looks redundant, but must be done this way due to
+ // setState being an asynchronous call
var items = e.clipboardData.items;
var numItems = 0;
if (items) {
@@ -87,9 +87,9 @@ module.exports = React.createClass({
}
}
- self.props.setUploads(numItems);
+ var numToUpload = self.props.setUploads(numItems);
- for (var i = 0; i < items.length; i++) {
+ for (var i = 0; i < items.length && i < numToUpload; i++) {
if (items[i].type.indexOf("image") !== -1) {
var file = items[i].getAsFile();
diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx
index 334591ee3..69e565185 100644
--- a/web/react/components/get_link_modal.jsx
+++ b/web/react/components/get_link_modal.jsx
@@ -42,7 +42,7 @@ module.exports = React.createClass({
</div>
<div className="modal-footer">
<button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
- <button data-copy-btn type="button" className="btn btn-primary" data-clipboard-text={this.state.value}>Copy Link</button>
+ <button data-copy-btn type="button" className="btn btn-primary pull-left" data-clipboard-text={this.state.value}>Copy Link</button>
</div>
</div>
</div>
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index 85df5f797..3b6f96c2d 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -37,7 +37,7 @@ var FindTeamDomain = React.createClass({
window.location.href = window.location.protocol + "//" + domain + "." + utils.getDomainWithOutSub();
}
else {
- this.state.server_error = "We couldn't find your " + strings.TeamPlural + ".";
+ this.state.server_error = "We couldn't find your " + strings.Team + ".";
this.setState(this.state);
}
}.bind(this),
diff --git a/web/react/components/mention.jsx b/web/react/components/mention.jsx
index ba758688b..3c33ddf49 100644
--- a/web/react/components/mention.jsx
+++ b/web/react/components/mention.jsx
@@ -6,10 +6,16 @@ module.exports = React.createClass({
this.props.handleClick(this.props.username);
},
render: function() {
+ var icon;
+ if (this.props.id != null) {
+ icon = <span><img className="mention-img" src={"/api/v1/users/" + this.props.id + "/image"}/></span>;
+ } else {
+ icon = <span><i className="mention-img fa fa-users fa-2x"></i></span>;
+ }
return (
<div className="mentions-name" onClick={this.handleClick}>
- <img className="pull-left mention-img" src={"/api/v1/users/" + this.props.id + "/image"}/>
- <span>@{this.props.username}</span><span style={{'color':'grey', 'marginLeft':'10px'}}>{this.props.name}</span>
+ <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 8b7e25b04..2fecc129a 100644
--- a/web/react/components/mention_list.jsx
+++ b/web/react/components/mention_list.jsx
@@ -23,6 +23,11 @@ module.exports = React.createClass({
}
}
);
+ $(document).click(function() {
+ if($('#'+self.props.id).length && $('#'+self.props.id).get(0) !== $(':focus').get(0)) {
+ self.setState({mentionText: "-1"})
+ }
+ });
},
componentWillUnmount: function() {
PostStore.removeMentionDataChangeListener(this._onChange);
@@ -74,6 +79,18 @@ module.exports = React.createClass({
users.push(profiles[id]);
}
+ var all = {};
+ all.username = "all";
+ all.full_name = "";
+ all.secondary_text = "Notifies everyone in the team";
+ users.push(all);
+
+ var channel = {};
+ channel.username = "channel";
+ channel.full_name = "";
+ channel.secondary_text = "Notifies everyone in the channel";
+ users.push(channel);
+
users.sort(function(a,b) {
if (a.username < b.username) return -1;
if (a.username > b.username) return 1;
@@ -91,6 +108,7 @@ module.exports = React.createClass({
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
@@ -99,7 +117,7 @@ module.exports = React.createClass({
<Mention
ref={'mention' + index}
username={users[i].username}
- name={users[i].full_name}
+ secondary_text={users[i].secondary_text}
id={users[i].id}
handleClick={this.handleClick} />
);
diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx
index 2785dc8e0..182d8884d 100644
--- a/web/react/components/more_direct_channels.jsx
+++ b/web/react/components/more_direct_channels.jsx
@@ -49,7 +49,7 @@ module.exports = React.createClass({
<span aria-hidden="true">&times;</span>
<span className="sr-only">Close</span>
</button>
- <h4 className="modal-title">More Direct Messages</h4>
+ <h4 className="modal-title">More Private Messages</h4>
</div>
<div className="modal-body">
<ul className="nav nav-pills nav-stacked">
diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx
index afe978495..04b5ba082 100644
--- a/web/react/components/post.jsx
+++ b/web/react/components/post.jsx
@@ -6,13 +6,14 @@ var PostBody = require('./post_body.jsx');
var PostInfo = require('./post_info.jsx');
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Constants = require('../utils/constants.jsx');
+var UserStore = require('../stores/user_store.jsx');
var ActionTypes = Constants.ActionTypes;
module.exports = React.createClass({
componentDidMount: function() {
- $('.edit-modal').on('show.bs.modal', function () {
- $('.edit-modal .edit-modal-body').css('overflow-y', 'auto');
- $('.edit-modal .edit-modal-body').css('max-height', $(window).height() * 0.7);
+ $('.modal').on('show.bs.modal', function () {
+ $('.modal-body').css('overflow-y', 'auto');
+ $('.modal-body').css('max-height', $(window).height() * 0.7);
});
},
handleCommentClick: function(e) {
@@ -56,7 +57,7 @@ module.exports = React.createClass({
var error = this.state.error ? <div className='form-group has-error'><label className='control-label'>{ this.state.error }</label></div> : null;
- if(this.props.sameRoot){
+ if (this.props.sameRoot){
rootUser = "same--root";
}
else {
@@ -64,13 +65,18 @@ module.exports = React.createClass({
}
var postType = "";
- if(type != "Post"){
+ if (type != "Post"){
postType = "post--comment";
}
+ var currentUserCss = "";
+ if (UserStore.getCurrentId() === post.user_id) {
+ currentUserCss = "current--user";
+ }
+
return (
<div>
- <div id={post.id} className={"post " + this.props.sameUser + " " + rootUser + " " + postType}>
+ <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" />
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index 3079917ec..7d5ef4d33 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -102,7 +102,7 @@ module.exports = React.createClass({
images.push(filenames[i]);
} else if (i < Constants.MAX_DISPLAY_FILES) {
postFiles.push(
- <div className="post-image__column custom-file" key={fileInfo.name}>
+ <div className="post-image__column custom-file" key={fileInfo.name+i}>
<a href={fileInfo.path+"."+fileInfo.ext} download={fileInfo.name+"."+fileInfo.ext}>
<div className={"file-icon "+utils.getIconClassName(type)}/>
</a>
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index 37e3faef2..169efc766 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -295,7 +295,7 @@ module.exports = React.createClass({
},
render: function() {
var order = [];
- var posts = {};
+ var posts;
var last_viewed = Number.MAX_VALUE;
@@ -324,13 +324,7 @@ module.exports = React.createClass({
if (order.length > 0 && order.length % Constants.POST_CHUNK_SIZE === 0) {
more_messages = <a ref="loadmore" className="more-messages-text theme" href="#" onClick={this.getMorePosts}>Load more messages</a>;
} else if (channel.type === 'D') {
- var userIds = channel.name.split('__');
- var teammate;
- if (userIds.length === 2 && userIds[0] === user_id) {
- teammate = UserStore.getProfile(userIds[1]);
- } else if (userIds.length === 2 && userIds[1] === user_id) {
- teammate = UserStore.getProfile(userIds[0]);
- }
+ var teammate = utils.getDirectTeammate(channel.id)
if (teammate) {
var teammate_name = teammate.full_name.length > 0 ? teammate.full_name : teammate.username;
@@ -342,13 +336,13 @@ module.exports = React.createClass({
<div className="channel-intro-profile">
<strong><UserProfile userId={teammate.id} /></strong>
</div>
- <p className="channel-intro-text">{"This is the start of your direct message history with " + teammate_name + "." }<br/>{"Direct 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 {
more_messages = (
<div className="channel-intro">
- <p className="channel-intro-text">{"This is the start of your direct message history with this " + strings.Team + "mate. Direct 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 this " + strings.Team + "mate. Private messages and files shared here are not shown to people outside this area."}</p>
</div>
);
}
@@ -356,6 +350,7 @@ module.exports = React.createClass({
var ui_name = channel.display_name
var members = ChannelStore.getCurrentExtraInfo().members;
var creator_name = "";
+ var userStyle = { color: UserStore.getCurrentUser().props.theme }
for (var i = 0; i < members.length; i++) {
if (members[i].roles.indexOf('admin') > -1) {
@@ -382,8 +377,18 @@ module.exports = React.createClass({
</p>
</div>
);
+ } else if (channel.name === Constants.OFFTOPIC_CHANNEL) {
+ more_messages = (
+ <div className="channel-intro">
+ <h4 className="channel-intro-title">Welcome</h4>
+ <p>
+ {"This is the start of " + ui_name + ", a channel for conversations you’d prefer out of more focused channels."}
+ <br/>
+ <a className="intro-links" href="#" style={userStyle} data-toggle="modal" data-target="#edit_channel" data-desc={channel.description} data-title={ui_name} data-channelid={channel.id}><i className="fa fa-pencil"></i>Set a description</a>
+ </p>
+ </div>
+ );
} else {
- var userStyle = { color: UserStore.getCurrentUser().props.theme }
var ui_type = channel.type === 'P' ? "private group" : "channel";
more_messages = (
<div className="channel-intro">
@@ -403,55 +408,70 @@ module.exports = React.createClass({
}
var postCtls = [];
- var previousPostDay = posts[order[order.length-1]] ? utils.getDateForUnixTicks(posts[order[order.length-1]].create_at): new Date();
- var currentPostDay = new Date();
- for (var i = order.length-1; i >= 0; i--) {
- var post = posts[order[i]];
- var parentPost;
+ if (posts != undefined) {
+ var previousPostDay = posts[order[order.length-1]] ? utils.getDateForUnixTicks(posts[order[order.length-1]].create_at): new Date();
+ var currentPostDay = new Date();
- if (post.parent_id) {
- parentPost = posts[post.parent_id];
- } else {
- parentPost = null;
- }
+ for (var i = order.length-1; i >= 0; i--) {
+ var post = posts[order[i]];
+ var parentPost;
- 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 (post.parent_id) {
+ parentPost = posts[post.parent_id];
+ } else {
+ parentPost = null;
+ }
- // 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 === '';
+ 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;
- // 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);
- }
+ // 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 === '';
- var postCtl = <Post sameUser={sameUser} sameRoot={sameRoot} post={post} parentPost={parentPost} key={post.id} posts={posts} hideProfilePic={hideProfilePic} isLastComment={isLastComment} />;
+ // 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);
+ }
- currentPostDay = utils.getDateForUnixTicks(post.create_at);
- if(currentPostDay.getDate() !== previousPostDay.getDate() || currentPostDay.getMonth() !== previousPostDay.getMonth() || currentPostDay.getFullYear() !== previousPostDay.getFullYear()) {
- postCtls.push(
- <div className="date-separator">
- <hr className="separator__hr" />
- <div className="separator__text">{currentPostDay.toDateString()}</div>
- </div>
- );
- }
+ var postCtl = <Post sameUser={sameUser} sameRoot={sameRoot} post={post} parentPost={parentPost} key={post.id} posts={posts} hideProfilePic={hideProfilePic} isLastComment={isLastComment} />;
- if (post.create_at > last_viewed && !rendered_last_viewed) {
- rendered_last_viewed = true;
- postCtls.push(
- <div className="new-separator">
- <hr id="new_message" className="separator__hr" />
- <div className="separator__text">New Messages</div>
- </div>
- );
+ currentPostDay = utils.getDateForUnixTicks(post.create_at);
+ if(currentPostDay.getDate() !== previousPostDay.getDate() || currentPostDay.getMonth() !== previousPostDay.getMonth() || currentPostDay.getFullYear() !== previousPostDay.getFullYear()) {
+ postCtls.push(
+ <div className="date-separator">
+ <hr className="separator__hr" />
+ <div className="separator__text">{currentPostDay.toDateString()}</div>
+ </div>
+ );
+ }
+
+ if (post.create_at > last_viewed && !rendered_last_viewed) {
+ rendered_last_viewed = true;
+ postCtls.push(
+ <div className="new-separator">
+ <hr id="new_message" className="separator__hr" />
+ <div className="separator__text">New Messages</div>
+ </div>
+ );
+ }
+ postCtls.push(postCtl);
+ previousPostDay = utils.getDateForUnixTicks(post.create_at);
}
- postCtls.push(postCtl);
- previousPostDay = utils.getDateForUnixTicks(post.create_at);
+ }
+ 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>
+ );
}
return (
diff --git a/web/react/components/post_right.jsx b/web/react/components/post_right.jsx
index 43be60afa..2c28c5d9f 100644
--- a/web/react/components/post_right.jsx
+++ b/web/react/components/post_right.jsx
@@ -68,9 +68,14 @@ RootPost = React.createClass({
var filenames = this.props.post.filenames;
var isOwner = UserStore.getCurrentId() == this.props.post.user_id;
- var type = "Post"
+ var type = "Post";
if (this.props.post.root_id.length > 0) {
- type = "Comment"
+ type = "Comment";
+ }
+
+ var currentUserCss = "";
+ if (UserStore.getCurrentId() === this.props.post.user_id) {
+ currentUserCss = "current--user";
}
if (filenames) {
@@ -84,7 +89,7 @@ RootPost = React.createClass({
if (fileSplit.length < 2) continue;
var ext = fileSplit[fileSplit.length-1];
- fileSplit.splice(fileSplit.length-1,1)
+ fileSplit.splice(fileSplit.length-1,1);
var filePath = fileSplit.join('.');
var filename = filePath.split('/')[filePath.split('/').length-1];
@@ -111,7 +116,7 @@ RootPost = React.createClass({
}
return (
- <div className="post post--root">
+ <div className={"post post--root " + currentUserCss}>
<div className="post-profile-img__container">
<img className="post-profile-img" src={"/api/v1/users/" + this.props.post.user_id + "/image"} height="36" width="36" />
</div>
@@ -170,6 +175,11 @@ CommentPost = React.createClass({
var commentClass = "post";
+ var currentUserCss = "";
+ if (UserStore.getCurrentId() === this.props.post.user_id) {
+ currentUserCss = "current--user";
+ }
+
var postImageModalId = "rhs_comment_view_image_modal_" + this.props.post.id;
var filenames = this.props.post.filenames;
var isOwner = UserStore.getCurrentId() == this.props.post.user_id;
@@ -219,7 +229,7 @@ CommentPost = React.createClass({
var message = utils.textToJsx(this.props.post.message);
return (
- <div className={commentClass}>
+ <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" />
</div>
diff --git a/web/react/components/search_results.jsx b/web/react/components/search_results.jsx
index 51aefd3b8..003a38b7e 100644
--- a/web/react/components/search_results.jsx
+++ b/web/react/components/search_results.jsx
@@ -43,6 +43,7 @@ SearchItem = React.createClass({
e.preventDefault();
var self = this;
+
client.getPost(
this.props.post.channel_id,
this.props.post.id,
@@ -64,6 +65,11 @@ SearchItem = React.createClass({
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 : "";
+
+ utils.switchChannel(postChannel,teammate);
},
render: function() {
@@ -73,7 +79,7 @@ SearchItem = React.createClass({
if (channel) {
if (channel.type === 'D') {
- channelName = "Direct Message";
+ channelName = "Private Message";
} else {
channelName = channel.display_name;
}
diff --git a/web/react/components/setting_item_max.jsx b/web/react/components/setting_item_max.jsx
index 03f05b0cf..b8b667e1a 100644
--- a/web/react/components/setting_item_max.jsx
+++ b/web/react/components/setting_item_max.jsx
@@ -13,7 +13,7 @@ module.exports = React.createClass({
<li className="col-sm-12 section-title">{this.props.title}</li>
<li className="col-sm-9 col-sm-offset-3">
<ul className="setting-list">
- <li className="row setting-list-item form-group">
+ <li className="setting-list-item">
{inputs}
</li>
<li className="setting-list-item">
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 10017c7ee..0e4d38fe0 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -269,13 +269,8 @@ var SidebarLoggedIn = React.createClass({
var channel = ChannelStore.getCurrent();
if (channel) {
if (channel.type === 'D') {
- userIds = channel.name.split('__');
- if (userIds.length < 2) return;
- if (userIds[0] == UserStore.getCurrentId() && UserStore.getProfile(userIds[1])) {
- document.title = UserStore.getProfile(userIds[1]).username + " " + document.title.substring(document.title.lastIndexOf("-"));
- } else if (userIds[1] == UserStore.getCurrentId() && UserStore.getProfile(userIds[0])) {
- document.title = UserStore.getProfile(userIds[0]).username + " " + document.title.substring(document.title.lastIndexOf("-"));
- }
+ var teammate_username = utils.getDirectTeammate(channel.id).username
+ document.title = teammate_username + " " + document.title.substring(document.title.lastIndexOf("-"));
} else {
document.title = channel.display_name + " " + document.title.substring(document.title.lastIndexOf("-"))
}
@@ -414,7 +409,7 @@ var SidebarLoggedIn = React.createClass({
{privateChannelItems}
</ul>
<ul className="nav nav-pills nav-stacked">
- <li><h4>Direct Messages</h4></li>
+ <li><h4>Private Messages</h4></li>
{directMessageItems}
{ this.state.hideDirectChannels.length > 0 ?
<li><a href="#" data-toggle="modal" className="nav-more" data-target="#more_direct_channels" data-channels={JSON.stringify(this.state.hideDirectChannels)}>{"More ("+this.state.hideDirectChannels.length+")"}</a></li>
diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx
index c523ce554..22d1d9ad2 100644
--- a/web/react/components/sidebar_right_menu.jsx
+++ b/web/react/components/sidebar_right_menu.jsx
@@ -60,6 +60,7 @@ module.exports = React.createClass({
<div className="nav-pills__container">
<ul className="nav nav-pills nav-stacked">
<li><a href="#" data-toggle="modal" data-target="#user_settings1"><i className="glyphicon glyphicon-cog"></i>Account Settings</a></li>
+ { isAdmin ? <li><a href="#" data-toggle="modal" data-target="#team_settings"><i className="glyphicon glyphicon-globe"></i>Team Settings</a></li> : "" }
{ invite_link }
{ team_link }
{ manage_link }
diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx
index 30fe92af5..587d8cb82 100644
--- a/web/react/components/signup_team_complete.jsx
+++ b/web/react/components/signup_team_complete.jsx
@@ -164,7 +164,9 @@ TeamUrlPage = React.createClass({
}
var cleaned_name = utils.cleanUpUrlable(name);
- if (cleaned_name != name) {
+
+ var urlRegex = /^[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+$/g;
+ if (cleaned_name != name || !urlRegex.test(name)) {
this.setState({name_error: "Must be lowercase alphanumeric characters"});
return;
}
@@ -337,7 +339,7 @@ EmailItem = React.createClass({
return false;
}
else if (email === teamEmail) {
- this.state.email_error = "Please use an a different email than the one used at signup";
+ this.state.email_error = "Please use a different email than the one used at signup";
this.setState(this.state);
return false;
}
diff --git a/web/react/components/team_settings.jsx b/web/react/components/team_settings.jsx
index 0cec30f3e..166b1f38b 100644
--- a/web/react/components/team_settings.jsx
+++ b/web/react/components/team_settings.jsx
@@ -78,13 +78,14 @@ var FeatureTab = React.createClass({
<button className={"btn btn-default "+valetActive[0]} onClick={function(){self.handleValetRadio("true")}}>On</button>
<button className={"btn btn-default "+valetActive[1]} onClick={function(){self.handleValetRadio("false")}}>Off</button>
</div>
- <div><br/>Warning: Turning on the Valet feature and using it with any third party software increases the risk of a security breach.</div>
+ <div><br/>Valet is a preview feature for enabling a non-user account limited to basic member permissions that can be manipulated by 3rd parties.<br/><br/>IMPORTANT: The preview version of Valet should not be used without a secure connection and a trusted 3rd party, since user credentials are used to connect. OAuth2 will be used in the final release.</div>
+ <br></br>
</div>
);
valetSection = (
<SettingItemMax
- title="Valet"
+ title="Valet (Preview - EXPERTS ONLY)"
inputs={inputs}
submit={this.submitValetFeature}
server_error={server_error}
@@ -102,7 +103,7 @@ var FeatureTab = React.createClass({
valetSection = (
<SettingItemMin
- title="Valet"
+ title="Valet (Preview - EXPERTS ONLY)"
describe={describe}
updateSection={function(){self.props.updateSection("valet");}}
/>
@@ -113,7 +114,7 @@ var FeatureTab = React.createClass({
<div>
<div className="modal-header">
<button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
- <h4 className="modal-title" ref="title"><i className="modal-back"></i>General Settings</h4>
+ <h4 className="modal-title" ref="title"><i className="modal-back"></i>Feature Settings</h4>
</div>
<div ref="wrapper" className="user-settings">
<h3 className="tab-header">Feature Settings</h3>
diff --git a/web/react/components/team_settings_modal.jsx b/web/react/components/team_settings_modal.jsx
index 08a952d2e..e50378b7f 100644
--- a/web/react/components/team_settings_modal.jsx
+++ b/web/react/components/team_settings_modal.jsx
@@ -45,7 +45,7 @@ module.exports = React.createClass({
updateTab={this.updateTab}
/>
</div>
- <div className="settings-content">
+ <div className="settings-content minimize-settings">
<TeamSettings
activeTab={this.state.active_tab}
activeSection={this.state.active_section}
diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx
index 7a4762e07..934e863a2 100644
--- a/web/react/components/textbox.jsx
+++ b/web/react/components/textbox.jsx
@@ -153,7 +153,7 @@ module.exports = React.createClass({
var mentions = [];
for (var i = 0; i < matches.length; i++) {
var m = matches[i].substring(1,matches[i].length).trim();
- if (m in profileMap && mentions.indexOf(m) === -1) {
+ if ((m in profileMap && mentions.indexOf(m) === -1) || Constants.SPECIAL_MENTIONS.indexOf(m) !== -1) {
mentions.push(m);
}
}
diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx
index 8ffad737d..648960471 100644
--- a/web/react/components/user_profile.jsx
+++ b/web/react/components/user_profile.jsx
@@ -10,8 +10,7 @@ function getStateFromStores(userId) {
if (profile == null) {
return { profile: { id: "0", username: "..."} };
- }
- else {
+ } else {
return { profile: profile };
}
}
@@ -54,12 +53,11 @@ module.exports = React.createClass({
var name = this.props.overwriteName ? this.props.overwriteName : this.state.profile.username;
- var data_content = ""
- data_content += "<img style='margin: 10px' src='/api/v1/users/" + this.state.profile.id + "/image' height='128' width='128' />"
+ var data_content = "<img style='margin: 10px' src='/api/v1/users/" + this.state.profile.id + "/image' height='128' width='128' />";
if (!config.ShowEmail) {
- data_content += "<div><span style='white-space:nowrap;'>Email not shared</span></div>";
+ data_content += "<div class='text-nowrap'>Email not shared</div>";
} else {
- data_content += "<div><a href='mailto:'" + this.state.profile.email + "'' style='white-space:nowrap;text-transform:lowercase;'>" + this.state.profile.email + "</a></div>";
+ data_content += "<div><a href='mailto:" + this.state.profile.email + "' class='text-nowrap text-lowercase'>" + this.state.profile.email + "</a></div>";
}
return (
diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx
index 7d542a8b7..b4c3747af 100644
--- a/web/react/components/user_settings.jsx
+++ b/web/react/components/user_settings.jsx
@@ -20,11 +20,10 @@ function getNotificationsStateFromStores() {
var mention_key = false;
var custom_keys = "";
var first_name_key = false;
+ var all_key = false;
+ var channel_key = false;
- if (!user.notify_props) {
- mention_keys = user.username;
- if (user.full_name.length > 0) mention_keys += ","+ user.full_name.split(" ")[0];
- } else {
+ if (user.notify_props) {
if (user.notify_props.mention_keys !== undefined) {
var keys = user.notify_props.mention_keys.split(',');
@@ -48,9 +47,17 @@ function getNotificationsStateFromStores() {
if (user.notify_props.first_name !== undefined) {
first_name_key = user.notify_props.first_name === "true";
}
+
+ if (user.notify_props.all !== undefined) {
+ all_key = user.notify_props.all === "true";
+ }
+
+ if (user.notify_props.channel !== undefined) {
+ channel_key = user.notify_props.channel === "true";
+ }
}
- return { notify_level: desktop, enable_email: email, enable_sound: sound, username_key: username_key, mention_key: mention_key, custom_keys: custom_keys, custom_keys_checked: custom_keys.length > 0, first_name_key: first_name_key };
+ return { notify_level: desktop, enable_email: email, enable_sound: sound, username_key: username_key, mention_key: mention_key, custom_keys: custom_keys, custom_keys_checked: custom_keys.length > 0, first_name_key: first_name_key, all_key: all_key, channel_key: channel_key };
}
@@ -73,6 +80,8 @@ var NotificationsTab = React.createClass({
data["mention_keys"] = string_keys;
data["first_name"] = this.state.first_name_key ? "true" : "false";
+ data["all"] = this.state.all_key ? "true" : "false";
+ data["channel"] = this.state.channel_key ? "true" : "false";
client.updateUserNotifyProps(data,
function(data) {
@@ -120,6 +129,12 @@ var NotificationsTab = React.createClass({
updateFirstNameKey: function(val) {
this.setState({ first_name_key: val });
},
+ updateAllKey: function(val) {
+ this.setState({ all_key: val });
+ },
+ updateChannelKey: function(val) {
+ this.setState({ channel_key: val });
+ },
updateCustomMentionKeys: function() {
var checked = this.refs.customcheck.getDOMNode().checked;
@@ -155,7 +170,7 @@ var NotificationsTab = React.createClass({
var inputs = [];
inputs.push(
- <div className="col-sm-12">
+ <div>
<div className="radio">
<label>
<input type="radio" checked={notifyActive[0]} onClick={function(){self.handleNotifyRadio("all")}}>For all activity</input>
@@ -164,7 +179,7 @@ var NotificationsTab = React.createClass({
</div>
<div className="radio">
<label>
- <input type="radio" checked={notifyActive[1]} onClick={function(){self.handleNotifyRadio("mention")}}>Only for mentions and direct messages</input>
+ <input type="radio" checked={notifyActive[1]} onClick={function(){self.handleNotifyRadio("mention")}}>Only for mentions and private messages</input>
</label>
<br/>
</div>
@@ -188,7 +203,7 @@ var NotificationsTab = React.createClass({
} else {
var describe = "";
if (this.state.notify_level === "mention") {
- describe = "Only for mentions and direct messages";
+ describe = "Only for mentions and private messages";
} else if (this.state.notify_level === "none") {
describe = "Never";
} else {
@@ -216,7 +231,7 @@ var NotificationsTab = React.createClass({
var inputs = [];
inputs.push(
- <div className="col-sm-12">
+ <div>
<div className="btn-group" data-toggle="buttons-radio">
<button className={"btn btn-default "+soundActive[0]} onClick={function(){self.handleSoundRadio("true")}}>On</button>
<button className={"btn btn-default "+soundActive[1]} onClick={function(){self.handleSoundRadio("false")}}>Off</button>
@@ -262,12 +277,12 @@ var NotificationsTab = React.createClass({
var inputs = [];
inputs.push(
- <div className="col-sm-12">
+ <div>
<div className="btn-group" data-toggle="buttons-radio">
<button className={"btn btn-default "+emailActive[0]} onClick={function(){self.handleEmailRadio("true")}}>On</button>
<button className={"btn btn-default "+emailActive[1]} onClick={function(){self.handleEmailRadio("false")}}>Off</button>
</div>
- <div><br/>{"Email notifications are sent for mentions and direct messages after you have been away from " + config.SiteName + " for 5 minutes."}</div>
+ <div><br/>{"Email notifications are sent for mentions and private messages after you have been away from " + config.SiteName + " for 5 minutes."}</div>
</div>
);
@@ -309,7 +324,7 @@ var NotificationsTab = React.createClass({
if (first_name != "") {
inputs.push(
- <div className="col-sm-12">
+ <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>
@@ -320,7 +335,7 @@ var NotificationsTab = React.createClass({
}
inputs.push(
- <div className="col-sm-12">
+ <div>
<div className="checkbox">
<label>
<input type="checkbox" checked={this.state.username_key} onChange={function(e){self.updateUsernameKey(e.target.checked);}}>{'Your non-case sensitive username "' + user.username + '"'}</input>
@@ -330,7 +345,7 @@ var NotificationsTab = React.createClass({
);
inputs.push(
- <div className="col-sm-12">
+ <div>
<div className="checkbox">
<label>
<input type="checkbox" checked={this.state.mention_key} onChange={function(e){self.updateMentionKey(e.target.checked);}}>{'Your username mentioned "@' + user.username + '"'}</input>
@@ -340,7 +355,27 @@ var NotificationsTab = React.createClass({
);
inputs.push(
- <div className="col-sm-12">
+ <div>
+ <div className="checkbox">
+ <label>
+ <input type="checkbox" checked={this.state.all_key} onChange={function(e){self.updateAllKey(e.target.checked);}}>{'Team-wide mentions "@all"'}</input>
+ </label>
+ </div>
+ </div>
+ );
+
+ inputs.push(
+ <div>
+ <div className="checkbox">
+ <label>
+ <input type="checkbox" checked={this.state.channel_key} onChange={function(e){self.updateChannelKey(e.target.checked);}}>{'Channel-wide mentions "@channel"'}</input>
+ </label>
+ </div>
+ </div>
+ );
+
+ inputs.push(
+ <div>
<div className="checkbox">
<label>
<input ref="customcheck" type="checkbox" checked={this.state.custom_keys_checked} onChange={this.updateCustomMentionKeys}>{'Other non-case sensitive words, separated by commas:'}</input>
@@ -369,6 +404,8 @@ var NotificationsTab = React.createClass({
}
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.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(','));
var describe = "";
@@ -622,7 +659,7 @@ var SecurityTab = React.createClass({
var inputs = [];
inputs.push(
- <div>
+ <div className="form-group">
<label className="col-sm-5 control-label">Current Password</label>
<div className="col-sm-7">
<input className="form-control" type="password" onChange={this.updateCurrentPassword} value={this.state.current_password}/>
@@ -630,7 +667,7 @@ var SecurityTab = React.createClass({
</div>
);
inputs.push(
- <div>
+ <div className="form-group">
<label className="col-sm-5 control-label">New Password</label>
<div className="col-sm-7">
<input className="form-control" type="password" onChange={this.updateNewPassword} value={this.state.new_password}/>
@@ -638,7 +675,7 @@ var SecurityTab = React.createClass({
</div>
);
inputs.push(
- <div>
+ <div className="form-group">
<label className="col-sm-5 control-label">Retype New Password</label>
<div className="col-sm-7">
<input className="form-control" type="password" onChange={this.updateConfirmPassword} value={this.state.confirm_password}/>
@@ -772,6 +809,11 @@ var GeneralTab = React.createClass({
if(!this.submitActive) return;
+ if(this.state.picture.type !== "image/jpeg") {
+ this.setState({client_error: "Only JPG images may be used for profile pictures"});
+ return;
+ }
+
formData = new FormData();
formData.append('image', this.state.picture, this.state.picture.name);
@@ -802,11 +844,13 @@ var GeneralTab = React.createClass({
updatePicture: function(e) {
if (e.target.files && e.target.files[0]) {
this.setState({ picture: e.target.files[0] });
+
+ this.submitActive = true;
+ this.setState({client_error:null})
+
} else {
this.setState({ picture: null });
}
-
- this.submitActive = true
},
updateSection: function(section) {
this.setState({client_error:""})
@@ -837,7 +881,7 @@ var GeneralTab = React.createClass({
var inputs = [];
inputs.push(
- <div>
+ <div className="form-group">
<label className="col-sm-5 control-label">First Name</label>
<div className="col-sm-7">
<input className="form-control" type="text" onChange={this.updateFirstName} value={this.state.first_name}/>
@@ -846,7 +890,7 @@ var GeneralTab = React.createClass({
);
inputs.push(
- <div>
+ <div className="form-group">
<label className="col-sm-5 control-label">Last Name</label>
<div className="col-sm-7">
<input className="form-control" type="text" onChange={this.updateLastName} value={this.state.last_name}/>
@@ -879,7 +923,7 @@ var GeneralTab = React.createClass({
var inputs = [];
inputs.push(
- <div>
+ <div className="form-group">
<label className="col-sm-5 control-label">{utils.isMobile() ? "": "Username"}</label>
<div className="col-sm-7">
<input className="form-control" type="text" onChange={this.updateUsername} value={this.state.username}/>
@@ -911,7 +955,7 @@ var GeneralTab = React.createClass({
var inputs = [];
inputs.push(
- <div>
+ <div className="form-group">
<label className="col-sm-5 control-label">Primary Email</label>
<div className="col-sm-7">
<input className="form-control" type="text" onChange={this.updateEmail} value={this.state.email}/>
@@ -947,7 +991,7 @@ var GeneralTab = React.createClass({
submit={this.submitPicture}
src={"/api/v1/users/" + user.id + "/image"}
server_error={server_error}
- client_error={email_error}
+ client_error={client_error}
updateSection={function(e){self.updateSection("");e.preventDefault();}}
picture={this.state.picture}
pictureChange={this.updatePicture}
@@ -1048,7 +1092,7 @@ var AppearanceTab = React.createClass({
var inputs = [];
inputs.push(
- <li className="row setting-list-item form-group">
+ <li className="setting-list-item">
<div className="btn-group" data-toggle="buttons-radio">
{ theme_buttons }
</div>
diff --git a/web/react/components/user_settings_modal.jsx b/web/react/components/user_settings_modal.jsx
index ff001611d..1761e575a 100644
--- a/web/react/components/user_settings_modal.jsx
+++ b/web/react/components/user_settings_modal.jsx
@@ -50,7 +50,7 @@ module.exports = React.createClass({
updateTab={this.updateTab}
/>
</div>
- <div className="settings-content">
+ <div className="settings-content minimize-settings">
<UserSettings
activeTab={this.state.active_tab}
activeSection={this.state.active_section}
diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx
index bbca92c84..e1df4879f 100644
--- a/web/react/stores/user_store.jsx
+++ b/web/react/stores/user_store.jsx
@@ -240,6 +240,9 @@ var UserStore = assign({}, EventEmitter.prototype, {
if (first.length > 0) keys.push(first);
}
+ if (user.notify_props.all === "true") keys.push('@all');
+ if (user.notify_props.channel === "true") keys.push('@channel');
+
return keys;
} else {
return [];
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 4a0d243e2..3aadfb4b0 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -36,6 +36,7 @@ module.exports = {
SERVER_ACTION: null,
VIEW_ACTION: null
}),
+ SPECIAL_MENTIONS: ['all', 'channel'],
CHARACTER_LIMIT: 4000,
IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png'],
AUDIO_TYPES: ['mp3', 'wav', 'wma', 'm4a', 'flac', 'aac'],
@@ -48,8 +49,10 @@ module.exports = {
PATCH_TYPES: ['patch'],
ICON_FROM_TYPE: {'audio': 'audio', 'video': 'video', 'spreadsheet': 'ppt', 'pdf': 'pdf', 'code': 'code' , 'word': 'word' , 'excel': 'excel' , 'patch': 'patch', 'other': 'generic'},
MAX_DISPLAY_FILES: 5,
+ MAX_UPLOAD_FILES: 5,
MAX_FILE_SIZE: 50000000, // 50 MB
DEFAULT_CHANNEL: 'town-square',
+ OFFTOPIC_CHANNEL: 'off-topic',
POST_CHUNK_SIZE: 60,
RESERVED_DOMAINS: [
"www",
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 75c583c8f..f8a7d6450 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -2,6 +2,7 @@
// See License.txt for license information.
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+var ChannelStore = require('../stores/channel_store.jsx')
var UserStore = require('../stores/user_store.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
@@ -395,14 +396,10 @@ module.exports.textToJsx = function(text, options) {
var inner = [];
- // Function specific regexes
- var hashRegex = /^href="#[^"]+"|(#[A-Za-z]+[A-Za-z0-9_]*[A-Za-z0-9])$/g;
+ // Function specific regex
+ var hashRegex = /^href="#[^"]+"|(#[A-Za-z]+[A-Za-z0-9_\-]*[A-Za-z0-9])$/g;
- var implicitKeywords = {};
- var keywordArray = UserStore.getCurrentMentionKeys();
- for (var i = 0; i < keywordArray.length; i++) {
- implicitKeywords[keywordArray[i]] = true;
- }
+ var implicitKeywords = UserStore.getCurrentMentionKeys();
var lines = text.split("\n");
var urlMatcher = new LinkifyIt();
@@ -421,10 +418,13 @@ module.exports.textToJsx = function(text, options) {
highlightSearchClass = " search-highlight";
}
- if (explicitMention && UserStore.getProfileByUsername(explicitMention[1])) {
+ if (explicitMention &&
+ (UserStore.getProfileByUsername(explicitMention[1]) ||
+ Constants.SPECIAL_MENTIONS.indexOf(explicitMention[1]) !== -1))
+ {
var name = explicitMention[1];
// do both a non-case sensitive and case senstive check
- var mClass = (name.toLowerCase() in implicitKeywords || name in implicitKeywords) ? mentionClass : "";
+ var mClass = implicitKeywords.indexOf('@'+name.toLowerCase()) !== -1 || implicitKeywords.indexOf('@'+name) !== -1 ? mentionClass : "";
var suffix = word.match(puncEndRegex);
var prefix = word.match(puncStartRegex);
@@ -446,7 +446,7 @@ module.exports.textToJsx = function(text, options) {
} else if (trimWord.match(hashRegex)) {
var suffix = word.match(puncEndRegex);
var prefix = word.match(puncStartRegex);
- var mClass = trimWord in implicitKeywords || trimWord.toLowerCase() in implicitKeywords ? mentionClass : "";
+ var mClass = implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1 ? mentionClass : "";
if (searchTerm === trimWord.substring(1).toLowerCase() || searchTerm === trimWord.toLowerCase()) {
highlightSearchClass = " search-highlight";
@@ -454,7 +454,7 @@ module.exports.textToJsx = function(text, options) {
inner.push(<span key={word+i+z+"_span"}>{prefix}<a key={word+i+z+"_hash"} className={"theme " + mClass + highlightSearchClass} href="#" onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(trimWord)}>{trimWord}</a>{suffix} </span>);
- } else if (trimWord in implicitKeywords || trimWord.toLowerCase() in implicitKeywords) {
+ } else if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) {
var suffix = word.match(puncEndRegex);
var prefix = word.match(puncStartRegex);
@@ -727,6 +727,25 @@ module.exports.isComment = function(post) {
return false;
}
+module.exports.getDirectTeammate = function(channel_id) {
+ var userIds = ChannelStore.get(channel_id).name.split('__');
+ var curUserId = UserStore.getCurrentId();
+ var teammate = {};
+
+ if(userIds.length != 2 || userIds.indexOf(curUserId) === -1) {
+ return teammate;
+ }
+
+ for (var idx in userIds) {
+ if(userIds[idx] !== curUserId) {
+ teammate = UserStore.getProfile(userIds[idx]);
+ break;
+ }
+ }
+
+ return teammate;
+}
+
Image.prototype.load = function(url, progressCallback) {
var thisImg = this;
var xmlHTTP = new XMLHttpRequest();
diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss
index 5808aeb44..cf28e44e8 100644
--- a/web/sass-files/sass/partials/_base.scss
+++ b/web/sass-files/sass/partials/_base.scss
@@ -3,7 +3,7 @@ html, body {
}
body {
- font-family: 'Lato', sans-serif;
+ font-family: 'Open Sans', sans-serif;
-webkit-font-smoothing: antialiased;
background: #e9e9e9;
position: relative;
@@ -11,21 +11,29 @@ body {
&.white {
background: #fff;
.inner__wrap {
- height: 100%;
+ > .row.content {
+ min-height: 100%;
+ margin-bottom: -89px;
+ }
}
- .row.content {
- min-height: 100%;
- height: auto !important;
+ }
+ .inner__wrap {
+ height: 100%;
+ > .row.main {
height: 100%;
- margin-bottom: -89px;
}
}
> .container-fluid {
@include clearfix;
+ height: 100%;
position: relative;
}
}
+b, strong {
+ font-weight: 600;
+}
+
a {
word-break: break-word;
}
@@ -142,7 +150,7 @@ div.theme {
top: 0;
color: #FFF;
font-size: 20px;
- font-weight: bold;
+ font-weight: 600;
text-decoration: none;
padding: 0 10px;
}
diff --git a/web/sass-files/sass/partials/_headers.scss b/web/sass-files/sass/partials/_headers.scss
index 89bbaef2b..1ec1109a5 100644
--- a/web/sass-files/sass/partials/_headers.scss
+++ b/web/sass-files/sass/partials/_headers.scss
@@ -65,7 +65,7 @@
float:left;
}
.channel-intro-title {
- font-weight:bold;
+ font-weight:600;
}
.channel-intro-text {
margin-top:35px;
@@ -88,7 +88,7 @@
}
.dropdown-menu {
li a {
- padding: 3 20px;
+ padding: 3px 20px;
color: #555;
}
}
diff --git a/web/sass-files/sass/partials/_loading.scss b/web/sass-files/sass/partials/_loading.scss
new file mode 100644
index 000000000..185a42180
--- /dev/null
+++ b/web/sass-files/sass/partials/_loading.scss
@@ -0,0 +1,68 @@
+.loading-screen {
+ display: table;
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ @include box-sizing(border-box);
+ text-align: center;
+ .loading__content {
+ display: table-cell;
+ vertical-align: middle;
+ h3 {
+ font-weight: 400;
+ margin: 0 0.2em 0;
+ display: inline-block;
+ }
+ }
+}
+
+.loading-screen {
+ .loading__content {
+ .round {
+ background-color: #444;
+ width: 4px;
+ height: 4px;
+ display: inline-block;
+ margin: 0 1px;
+ 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;
+ }
+
+ #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;
+ }
+
+ 100% {
+ opacity: 0.1;
+ };
+ }
+
+ @-webkit-keyframes move {
+ 0% {
+ opacity: 1;
+ }
+
+ 100% {
+ opacity: 0.1;
+ };
+ }
+ }
+}
diff --git a/web/sass-files/sass/partials/_mentions.scss b/web/sass-files/sass/partials/_mentions.scss
index 11cd4e9e4..d6e2ab368 100644
--- a/web/sass-files/sass/partials/_mentions.scss
+++ b/web/sass-files/sass/partials/_mentions.scss
@@ -3,21 +3,19 @@
background: $primary-color;
position: relative;
z-index: 10;
- padding-bottom: 1px;
+ padding-bottom: 2px;
@include border-radius(3px);
- -moz-box-sizing: border-box;
- -webkit-box-sizing: border-box;
- box-sizing: border-box;
}
.mentions--top {
position: absolute;
- z-index:99999;
+ z-index: 1060;
.mentions-box {
position:absolute;
background-color:#fff;
- border:1px solid #ddd;
- overflow:scroll;
+ border: $border-gray;
+ overflow-x: hidden;
+ overflow-y: scroll;
bottom:0;
}
}
@@ -29,10 +27,10 @@
height:37px;
padding:2px;
z-index:101;
-}
-
-.mentions-name:hover {
- background-color:#e8eaed;
+ cursor: pointer;
+ &:hover {
+ background-color:#e8eaed;
+ }
}
.mentions-text {
@@ -46,6 +44,11 @@
border-radius: 10%;
}
+.mention-fullname {
+ color: grey;
+ padding-left: 10px;
+}
+
.mention-highlight {
background-color:#fff2bb;
}
@@ -53,3 +56,8 @@
.mention-link {
color:$primary-color;
}
+
+.mention-align {
+ position:relative;
+ top:5px;
+}
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index 43dbdc077..971ed0935 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -1,9 +1,17 @@
+.modal-body {
+ padding: 20px 15px;
+}
.modal {
&.image_modal {
.modal-backdrop.in {
@include opacity(0.7);
}
}
+ .info__label {
+ font-weight: 600;
+ text-align: right;
+ padding-right: 0;
+ }
.remove__member {
float: right;
}
@@ -13,7 +21,6 @@
margin-right: auto;
}
.modal-body {
- max-height: 75%;
overflow: auto;
}
.modal-push-down {
@@ -29,7 +36,7 @@
border-radius: 0;
background: $primary-color;
color: #FFF;
- padding: 15px 15px 11px;
+ padding: 15px 15px 11px;
border: none;
min-height: 56px;
@include clearfix;
@@ -41,14 +48,23 @@
margin: 0;
}
button.close {
- margin-top: 0;
+ margin: -2px -2px 0 0;
color: #fff;
@include opacity(1);
z-index: 5;
+ width: 30px;
+ height: 30px;
+ line-height: 30px;
+ @include single-transition(all, 0.25s, ease-in);
position: relative;
+ &:hover {
+ background: rgba(0, 0, 0, 0.1);
+ }
+ span {
+ line-height: 10px;
+ }
}
.btn {
- margin-right: 10px;
&.btn-primary {
float: right;
margin-top: -4px;
@@ -102,7 +118,7 @@
}
.more-channel-name {
color: #444;
- font-weight: bold;
+ font-weight: 600;
font-size: 0.95em;
}
tbody {
@@ -127,17 +143,19 @@
.modal-image {
position:relative;
width:100%;
+ height: 100%;
margin: 0 auto;
.image-wrapper {
- padding: 4px;
background: #FFF;
position: relative;
max-width: 80%;
- min-height: 280px;
min-width: 280px;
- @include border-radius(4px);
+ @include border-radius(3px);
display: table;
margin: 0 auto;
+ &:hover {
+ @include border-radius(3px 3px 0 0);
+ }
&:hover .modal-close {
@include opacity(1);
}
@@ -217,10 +235,11 @@
}
.modal-button-bar {
position:absolute;
- bottom:0px;
+ bottom:-40px;
left:0px;
right:0px;
- background-color:rgba(0, 0, 0, 0.8);
+ background-color: #222;
+ @include border-radius(0 0 3px 3px);
@include opacity(0);
-webkit-transition: opacity 0.6s;
-moz-transition: opacity 0.6s;
@@ -228,7 +247,6 @@
transition: opacity 0.6s;
line-height: 40px;
padding: 0 10px;
- margin: 4px;
&.footer--show {
@include opacity(1);
}
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index 745d50173..769cb1091 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -86,10 +86,11 @@ body.ios {
background: #FFF;
display: inline-block;
padding: 0 1em;
- font-weight: 900;
+ font-weight: 700;
@include border-radius(50px);
position: relative;
z-index: 5;
+ font-size: 13px;
}
}
.new-separator {
@@ -212,6 +213,12 @@ body.ios {
}
}
}
+ &.current--user {
+ .post-body {
+ @include border-radius(0 4px 4px 0);
+ background: #f5f5f5;
+ }
+ }
&.same--root {
.comment-icon__container {
@include opacity(0);
@@ -249,6 +256,7 @@ body.ios {
}
p {
margin: 0 0 5px;
+ font-size: 0.97em;
white-space: pre-wrap;
}
.comment-icon__container {
@@ -269,7 +277,7 @@ body.ios {
float: left;
.post-profile-img {
margin-right: 10px;
- @include border-radius(3px);
+ @include border-radius(50px);
}
}
&.post__content {
@@ -290,6 +298,8 @@ body.ios {
width: 600px;
float: left;
word-wrap: break-word;
+ padding: 0.3em 0.5em 0.1em;
+ margin: -0.3em 0 0;
.post-link {
@include clearfix;
text-overflow: ellipsis;
@@ -359,7 +369,7 @@ body.ios {
.embed-title {
margin: 3px 0 1px;
color: #555;
- font-weight: bold;
+ font-weight: 600;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index bed2f6324..0037879cf 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -5,7 +5,7 @@
&.post--comment {
&.other--root {
.post-comment {
- margin-left: 0;
+ margin-left: -7px;
}
}
}
@@ -22,11 +22,16 @@
width: 825px;
}
.post-body {
- padding-left: 0;
+ width: 736px;
border: none;
margin: 0;
}
}
+ .post-body {
+ float: none;
+ width: 750px;
+ margin: 0;
+ }
.post__content {
width: 920px;
}
@@ -63,10 +68,6 @@
}
}
}
- .post-body {
- float: none;
- width: 750px;
- }
}
}
}
@@ -81,7 +82,7 @@
&.post--comment {
&.other--root {
.post-comment {
- margin-left: 0;
+ margin-left: -7px;
}
}
}
@@ -89,6 +90,7 @@
margin-left: 60px;
padding-left: 10px;
border-left: 4px solid #EEE;
+ margin-bottom: 10px;
div.post-profile-img__container {
.post-profile-img {
display: none;
@@ -98,7 +100,7 @@
width: 825px;
}
.post-body {
- padding-left: 0;
+ width: 736px;
border: none;
margin: 0;
}
@@ -135,6 +137,7 @@
}
}
.post-body {
+ margin: 0;
float: none;
width: 750px;
}
@@ -231,17 +234,14 @@
}
}
.modal {
+ .info__label {
+ text-align: left;
+ padding-bottom: 5px;
+ }
.modal-header {
- padding-left: 20px;
- padding-right: 20px;
.modal-action {
margin-top: 10px;
}
- button.close {
- width: 35px;
- height: 32px;
- margin: -5px -10px 0;
- }
.modal-title {
float: none;
}
@@ -261,9 +261,11 @@
.settings-table {
display: block;
.settings-content {
- display: block;
- .section-edit {
- text-align: left;
+ &.minimize-settings {
+ display: block;
+ .section-edit {
+ text-align: left;
+ }
}
}
.settings-links {
@@ -287,10 +289,12 @@
}
.settings-table {
.settings-content {
- padding: 0;
- display: none;
- .user-settings {
- padding: 70px 20px 30px;
+ &.minimize-settings {
+ padding: 0;
+ display: none;
+ .user-settings {
+ padding: 70px 20px 30px;
+ }
}
}
.settings-links {
@@ -538,7 +542,6 @@
.modal {
.modal-image {
.image-wrapper {
- padding-bottom: 40px;
.modal-close {
@include opacity(1);
}
@@ -575,7 +578,7 @@
&.post--comment {
&.other--root {
.post-comment {
- margin-left: 11px;
+ margin-left: 4px;
}
}
}
diff --git a/web/sass-files/sass/partials/_search.scss b/web/sass-files/sass/partials/_search.scss
index ca5d25720..8d51d00c0 100644
--- a/web/sass-files/sass/partials/_search.scss
+++ b/web/sass-files/sass/partials/_search.scss
@@ -77,7 +77,7 @@
border: none;
}
.search-channel__name {
- font-weight: bold;
+ font-weight: 600;
margin: 0 0 10px 0;
}
}
diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss
index dbaab8b58..af759c650 100644
--- a/web/sass-files/sass/partials/_settings.scss
+++ b/web/sass-files/sass/partials/_settings.scss
@@ -56,7 +56,7 @@
.section-title {
margin-bottom: 5px;
- font-weight: bold;
+ font-weight: 600;
}
.section-edit {
@@ -172,7 +172,7 @@
border-top:1px solid lightgrey;
}
.post-profile-img {
- @include border-radius(3px);
+ @include border-radius(50px);
margin-right: 8px;
}
.member-name {
diff --git a/web/sass-files/sass/partials/_sidebar--left.scss b/web/sass-files/sass/partials/_sidebar--left.scss
index b1dd470d2..89d1ff416 100644
--- a/web/sass-files/sass/partials/_sidebar--left.scss
+++ b/web/sass-files/sass/partials/_sidebar--left.scss
@@ -59,7 +59,7 @@
color: #999;
&.unread-title {
color: #333;
- font-weight: bold;
+ font-weight: 600;
}
&:hover, &:focus {
background: #e6f2fa;
@@ -70,6 +70,7 @@
color: #111;
background-color: #e1e1e1;
border-radius: 0;
+ font-weight: 400;
}
}
}
diff --git a/web/sass-files/sass/styles.scss b/web/sass-files/sass/styles.scss
index 8446f1c01..9cc26320c 100644
--- a/web/sass-files/sass/styles.scss
+++ b/web/sass-files/sass/styles.scss
@@ -29,6 +29,7 @@
@import "partials/modal";
@import "partials/mentions";
@import "partials/error";
+@import "partials/loading";
// Responsive Css
@import "partials/responsive";
diff --git a/web/templates/channel.html b/web/templates/channel.html
index d10ae2304..d96aee3d4 100644
--- a/web/templates/channel.html
+++ b/web/templates/channel.html
@@ -1,4 +1,6 @@
+
{{define "channel"}}
+<!DOCTYPE html>
<html>
{{template "head" . }}
<body>
diff --git a/web/templates/find_team.html b/web/templates/find_team.html
index c731f7a8f..9acf3ac64 100644
--- a/web/templates/find_team.html
+++ b/web/templates/find_team.html
@@ -1,4 +1,5 @@
{{define "find_team"}}
+<!DOCTYPE html>
<html>
{{template "head" . }}
<body class="white">
diff --git a/web/templates/head.html b/web/templates/head.html
index 5eb7a7333..9c025d1f9 100644
--- a/web/templates/head.html
+++ b/web/templates/head.html
@@ -14,7 +14,7 @@
<link id="favicon" rel="icon" href="/static/images/favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="/static/images/favicon.ico" type="image/x-icon">
- <link href='https://fonts.googleapis.com/css?family=Lato:400,700,900' rel='stylesheet' type='text/css'>
+ <link href='http://fonts.googleapis.com/css?family=Open+Sans:400,600,700' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="/static/css/styles.css">
<script src="/static/js/min/perfect-scrollbar.min.js"></script>
diff --git a/web/templates/home.html b/web/templates/home.html
index 74f7a015b..abf8062f2 100644
--- a/web/templates/home.html
+++ b/web/templates/home.html
@@ -1,4 +1,5 @@
{{define "home"}}
+<!DOCTYPE html>
<html>
{{template "head" . }}
<body>
diff --git a/web/templates/login.html b/web/templates/login.html
index 1bc5394ab..c107e1ad5 100644
--- a/web/templates/login.html
+++ b/web/templates/login.html
@@ -1,4 +1,5 @@
{{define "login"}}
+<!DOCTYPE html>
<html>
{{template "head" . }}
<body class="white">
diff --git a/web/templates/password_reset.html b/web/templates/password_reset.html
index 1c5485e33..8b63556b1 100644
--- a/web/templates/password_reset.html
+++ b/web/templates/password_reset.html
@@ -1,4 +1,5 @@
{{define "password_reset"}}
+<!DOCTYPE html>
<html>
{{template "head" . }}
<body class="white">
diff --git a/web/templates/signup_team.html b/web/templates/signup_team.html
index f7e277340..fad332bee 100644
--- a/web/templates/signup_team.html
+++ b/web/templates/signup_team.html
@@ -1,4 +1,5 @@
{{define "signup_team"}}
+<!DOCTYPE html>
<html>
{{template "head" . }}
<body class="white">
diff --git a/web/templates/signup_team_complete.html b/web/templates/signup_team_complete.html
index aad521cb3..59f49cdbd 100644
--- a/web/templates/signup_team_complete.html
+++ b/web/templates/signup_team_complete.html
@@ -1,4 +1,5 @@
{{define "signup_team_complete"}}
+<!DOCTYPE html>
<html>
{{template "head" . }}
<body class="white">
diff --git a/web/templates/signup_team_confirm.html b/web/templates/signup_team_confirm.html
index a34c39ab6..9e21126da 100644
--- a/web/templates/signup_team_confirm.html
+++ b/web/templates/signup_team_confirm.html
@@ -1,4 +1,5 @@
{{define "signup_team_confirm"}}
+<!DOCTYPE html>
<html>
{{template "head" . }}
<body class="white">
diff --git a/web/templates/signup_user_complete.html b/web/templates/signup_user_complete.html
index a6827bc3a..5fe907ba7 100644
--- a/web/templates/signup_user_complete.html
+++ b/web/templates/signup_user_complete.html
@@ -1,4 +1,5 @@
{{define "signup_user_complete"}}
+<!DOCTYPE html>
<html>
{{template "head" . }}
<body class="white">
diff --git a/web/templates/verify.html b/web/templates/verify.html
index 60a7990f0..a61964bb3 100644
--- a/web/templates/verify.html
+++ b/web/templates/verify.html
@@ -1,4 +1,5 @@
{{define "verify"}}
+<!DOCTYPE html>
<html>
{{template "head" . }}
<body>
diff --git a/web/templates/welcome.html b/web/templates/welcome.html
index 27bf4bcaf..bab7a135d 100644
--- a/web/templates/welcome.html
+++ b/web/templates/welcome.html
@@ -1,4 +1,5 @@
{{define "welcome"}}
+<!DOCTYPE html>
<html>
{{template "head" . }}
<body>
diff --git a/web/web.go b/web/web.go
index 7357124b5..443a75916 100644
--- a/web/web.go
+++ b/web/web.go
@@ -285,7 +285,7 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) {
otherUserId = ids[0]
}
- if sc, err := api.CreateDirectChannel(c, otherUserId, r.URL.Path); err != nil {
+ if sc, err := api.CreateDirectChannel(c, otherUserId); err != nil {
api.Handle404(w, r)
return
} else {