summaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
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/file_preview.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.jsx15
-rw-r--r--web/react/components/post_body.jsx2
-rw-r--r--web/react/components/post_list.jsx13
-rw-r--r--web/react/components/setting_item_max.jsx2
-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.jsx2
-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_settings.jsx77
-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.jsx2
-rw-r--r--web/react/utils/utils.jsx21
-rw-r--r--web/sass-files/sass/partials/_mentions.scss5
-rw-r--r--web/sass-files/sass/partials/_modal.scss35
-rw-r--r--web/sass-files/sass/partials/_responsive.scss30
-rw-r--r--web/web.go2
24 files changed, 353 insertions, 147 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..fa9ab42ae 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 className="col-sm-12">
+ <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 className="col-sm-12">
+ <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 className="col-sm-12">
+ <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/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/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 86a423138..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="mention-img" src={"/api/v1/users/" + this.props.id + "/image"}/>
- <span>@{this.props.username}</span><span className="mention-fullname">{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..eb21e0efe 100644
--- a/web/react/components/mention_list.jsx
+++ b/web/react/components/mention_list.jsx
@@ -74,6 +74,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 +103,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 +112,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/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..e37de8d88 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -356,6 +356,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 +383,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">
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_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..a43e5d2f0 100644
--- a/web/react/components/team_settings.jsx
+++ b/web/react/components/team_settings.jsx
@@ -113,7 +113,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_settings.jsx b/web/react/components/user_settings.jsx
index 7d542a8b7..f97a06db3 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>
@@ -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,7 +277,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 "+emailActive[0]} onClick={function(){self.handleEmailRadio("true")}}>On</button>
<button className={"btn btn-default "+emailActive[1]} onClick={function(){self.handleEmailRadio("false")}}>Off</button>
@@ -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}/>
@@ -837,7 +874,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 +883,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 +916,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 +948,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}/>
@@ -1048,7 +1085,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 e5f42c8a0..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'],
@@ -51,6 +52,7 @@ module.exports = {
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..70a47742f 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -395,14 +395,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 +417,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 +445,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 +453,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);
diff --git a/web/sass-files/sass/partials/_mentions.scss b/web/sass-files/sass/partials/_mentions.scss
index 86fe400c9..d6e2ab368 100644
--- a/web/sass-files/sass/partials/_mentions.scss
+++ b/web/sass-files/sass/partials/_mentions.scss
@@ -56,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..4427cb7dd 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: bold;
+ text-align: right;
+ padding-right: 0;
+ }
.remove__member {
float: right;
}
@@ -29,7 +37,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,11 +49,21 @@
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;
@@ -129,15 +147,16 @@
width: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 +236,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 +248,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/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index bed2f6324..25533c770 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -231,17 +231,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 +258,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 +286,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 +539,6 @@
.modal {
.modal-image {
.image-wrapper {
- padding-bottom: 40px;
.modal-close {
@include opacity(1);
}
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 {