summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
author=Corey Hulen <corey@hulen.com>2015-10-19 10:25:51 -0700
committer=Corey Hulen <corey@hulen.com>2015-10-19 10:25:51 -0700
commit468f01dc895000211e62eb8a16feaa84ceb354ed (patch)
tree4868b46316bb79cf5c127f5b0638f3383a7c971c
parenta8f3f76c592928a0907fbaddd71ab6b8f68d28d6 (diff)
parentea1b3129688dd76c80c7d41eec4f2eb60f9f0639 (diff)
downloadchat-468f01dc895000211e62eb8a16feaa84ceb354ed.tar.gz
chat-468f01dc895000211e62eb8a16feaa84ceb354ed.tar.bz2
chat-468f01dc895000211e62eb8a16feaa84ceb354ed.zip
Merging from master
-rw-r--r--CHANGELOG.md6
-rw-r--r--README.md10
-rw-r--r--api/channel.go7
-rw-r--r--api/web_conn.go25
-rw-r--r--api/web_team_hub.go31
-rw-r--r--doc/install/Production-Ubuntu.md3
-rw-r--r--doc/install/Troubleshooting.md5
-rw-r--r--model/message.go16
-rw-r--r--utils/mail.go14
-rw-r--r--web/react/components/activity_log_modal.jsx10
-rw-r--r--web/react/components/admin_console/admin_controller.jsx12
-rw-r--r--web/react/components/admin_console/admin_sidebar.jsx4
-rw-r--r--web/react/components/admin_console/email_settings.jsx8
-rw-r--r--web/react/components/admin_console/log_settings.jsx18
-rw-r--r--web/react/components/channel_header.jsx16
-rw-r--r--web/react/components/edit_post_modal.jsx4
-rw-r--r--web/react/components/more_channels.jsx6
-rw-r--r--web/react/components/more_direct_channels.jsx84
-rw-r--r--web/react/components/msg_typing.jsx15
-rw-r--r--web/react/components/popover_list_members.jsx24
-rw-r--r--web/react/components/post_body.jsx8
-rw-r--r--web/react/components/post_info.jsx2
-rw-r--r--web/react/components/post_list.jsx60
-rw-r--r--web/react/components/rhs_comment.jsx26
-rw-r--r--web/react/components/sidebar.jsx116
-rw-r--r--web/react/components/team_signup_username_page.jsx7
-rw-r--r--web/react/components/user_profile.jsx42
-rw-r--r--web/react/components/user_settings/user_settings_appearance.jsx10
-rw-r--r--web/react/components/user_settings/user_settings_display.jsx21
-rw-r--r--web/react/components/user_settings/user_settings_notifications.jsx65
-rw-r--r--web/react/components/view_image.jsx7
-rw-r--r--web/react/pages/admin_console.jsx7
-rw-r--r--web/react/stores/socket_store.jsx187
-rw-r--r--web/react/utils/constants.jsx12
-rw-r--r--web/react/utils/utils.jsx3
-rw-r--r--web/sass-files/sass/partials/_command-box.scss1
-rw-r--r--web/sass-files/sass/partials/_modal.scss76
-rw-r--r--web/sass-files/sass/partials/_responsive.scss7
-rw-r--r--web/sass-files/sass/partials/_settings.scss7
-rw-r--r--web/sass-files/sass/partials/_videos.scss7
-rw-r--r--web/templates/admin_console.html2
-rw-r--r--web/web.go14
42 files changed, 602 insertions, 403 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 425902cf0..0d550f4d1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,8 +4,14 @@
The "UNDER DEVELOPMENT" section of the Mattermost changelog appears in the product's `master` branch to note key changes committed to master and are on their way to the next stable release. When a stable release is pushed the "UNDER DEVELOPMENT" heading is removed from the final changelog of the release.
+- **Release candidate anticipated:** 2015-11-10
- **Final release anticipated:** 2015-11-16
+### Changes
+
+- IE 10 no longer supported since global share of IE 10 fell below 5%
+
+
## Release v1.1.0
Released: 2015-10-16
diff --git a/README.md b/README.md
index 08f01f717..eb273b7ec 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,12 @@ Please see the [features pages of the Mattermost website](http://www.mattermost.
- Attach sound, video and image files from mobile devices
- Define team-specific branding and color themes across your devices
+#### Self-Host Ready
+
+- Host and manage dozens of teams from a single Mattermost server
+- Easily manage your Mattermost server using a web-based System Console
+- Script setup and maintenance using Mattermost command line tools
+
## Learn More
- [Product Vision and Target Audiences](http://www.mattermost.org/vision/) - What we're solving and for whom are we building
@@ -38,7 +44,9 @@ Follow us on Twitter at [@MattermostHQ](https://twitter.com/mattermosthq).
## Installing Mattermost
-There are multiple ways to install Mattermost depending on your needs.
+Latest stable release of Mattermost is available from http://www.mattermost.org/download/, including binary distribution, and from install guides below.
+
+If you use Docker, you can [install Mattermost in a single-container preview in one line](https://github.com/mattermost/platform/blob/master/doc/install/Docker-Single-Container.md#one-line-docker-install).
#### Quick Start Install for Product Evaluation
diff --git a/api/channel.go b/api/channel.go
index adf125378..70f7eba4b 100644
--- a/api/channel.go
+++ b/api/channel.go
@@ -568,7 +568,7 @@ func updateLastViewedAt(c *Context, w http.ResponseWriter, r *http.Request) {
Srv.Store.Channel().UpdateLastViewedAt(id, c.Session.UserId)
- message := model.NewMessage(c.Session.TeamId, id, c.Session.UserId, model.ACTION_VIEWED)
+ message := model.NewMessage(c.Session.TeamId, id, c.Session.UserId, model.ACTION_CHANNEL_VIEWED)
message.Add("channel_id", id)
PublishAndForget(message)
@@ -777,9 +777,8 @@ func RemoveUserFromChannel(userIdToRemove string, removerUserId string, channel
UpdateChannelAccessCacheAndForget(channel.TeamId, userIdToRemove, channel.Id)
- message := model.NewMessage(channel.TeamId, "", userIdToRemove, model.ACTION_USER_REMOVED)
- message.Add("channel_id", channel.Id)
- message.Add("remover", removerUserId)
+ message := model.NewMessage(channel.TeamId, channel.Id, userIdToRemove, model.ACTION_USER_REMOVED)
+ message.Add("remover_id", removerUserId)
PublishAndForget(message)
return nil
diff --git a/api/web_conn.go b/api/web_conn.go
index a5099e520..50a003ace 100644
--- a/api/web_conn.go
+++ b/api/web_conn.go
@@ -92,24 +92,9 @@ func (c *WebConn) writePump() {
return
}
- if len(msg.ChannelId) > 0 {
- allowed, ok := c.ChannelAccessCache[msg.ChannelId]
- if !ok {
- allowed = hasPermissionsToChannel(Srv.Store.Channel().CheckPermissionsTo(c.TeamId, msg.ChannelId, c.UserId))
- c.ChannelAccessCache[msg.ChannelId] = allowed
- }
-
- if allowed {
- c.WebSocket.SetWriteDeadline(time.Now().Add(WRITE_WAIT))
- if err := c.WebSocket.WriteJSON(msg); err != nil {
- return
- }
- }
- } else {
- c.WebSocket.SetWriteDeadline(time.Now().Add(WRITE_WAIT))
- if err := c.WebSocket.WriteJSON(msg); err != nil {
- return
- }
+ c.WebSocket.SetWriteDeadline(time.Now().Add(WRITE_WAIT))
+ if err := c.WebSocket.WriteJSON(msg); err != nil {
+ return
}
case <-ticker.C:
@@ -121,9 +106,11 @@ func (c *WebConn) writePump() {
}
}
-func (c *WebConn) updateChannelAccessCache(channelId string) {
+func (c *WebConn) updateChannelAccessCache(channelId string) bool {
allowed := hasPermissionsToChannel(Srv.Store.Channel().CheckPermissionsTo(c.TeamId, channelId, c.UserId))
c.ChannelAccessCache[channelId] = allowed
+
+ return allowed
}
func hasPermissionsToChannel(sc store.StoreChannel) bool {
diff --git a/api/web_team_hub.go b/api/web_team_hub.go
index c57de550f..6a25b7d3d 100644
--- a/api/web_team_hub.go
+++ b/api/web_team_hub.go
@@ -53,7 +53,7 @@ func (h *TeamHub) Start() {
}
case msg := <-h.broadcast:
for webCon := range h.connections {
- if !(webCon.UserId == msg.UserId && msg.Action == model.ACTION_TYPING) {
+ if ShouldSendEvent(webCon, msg) {
select {
case webCon.Send <- msg:
default:
@@ -86,3 +86,32 @@ func (h *TeamHub) UpdateChannelAccessCache(userId string, channelId string) {
}
}
}
+
+func ShouldSendEvent(webCon *WebConn, msg *model.Message) bool {
+
+ if webCon.UserId == msg.UserId {
+ // Don't need to tell the user they are typing
+ if msg.Action == model.ACTION_TYPING {
+ return false
+ }
+ } else {
+ // Don't share a user's view events with other users
+ if msg.Action == model.ACTION_CHANNEL_VIEWED {
+ return false
+ }
+
+ // Only report events to a user who is the subject of the event, or is in the channel of the event
+ if len(msg.ChannelId) > 0 {
+ allowed, ok := webCon.ChannelAccessCache[msg.ChannelId]
+ if !ok {
+ allowed = webCon.updateChannelAccessCache(msg.ChannelId)
+ }
+
+ if !allowed {
+ return false
+ }
+ }
+ }
+
+ return true
+}
diff --git a/doc/install/Production-Ubuntu.md b/doc/install/Production-Ubuntu.md
index 87f8edb84..836af3995 100644
--- a/doc/install/Production-Ubuntu.md
+++ b/doc/install/Production-Ubuntu.md
@@ -160,10 +160,11 @@ exec bin/platform
proxy_set_header X-Forwarded-Ssl on;
```
## Finish Mattermost Server setup
+
1. Navigate to https://mattermost.example.com and create a team and user.
1. The first user in the system is automatically granted the `system_admin` role, which gives you access to the System Console.
1. From the `town-square` channel click the dropdown and choose the `System Console` option
-1. Update Email Settings. We recommend using an email sending service. The example below assumes AmazonSES.
+1. Update Email Settings. We recommend using an email sending service. The example shows how an Amazon SES setup might look (sample credentials shown below are not real).
* Set *Send Email Notifications* to true
* Set *Require Email Verification* to true
* Set *Feedback Name* to `No-Reply`
diff --git a/doc/install/Troubleshooting.md b/doc/install/Troubleshooting.md
index b87663ab3..6a7260ddf 100644
--- a/doc/install/Troubleshooting.md
+++ b/doc/install/Troubleshooting.md
@@ -5,3 +5,8 @@
1. **DO NOT manipulate the Mattermost database**
- In particular, DO NOT delete data from the database, as Mattermost is designed to stop working if data integrity has been compromised. The system is designed to archive content continously and generally assumes data is never deleted.
+
+#### Common Issues
+
+1. Error message in logs when attempting to sign-up: `x509: certificate signed by unknown authority`
+ - This error may appear when attempt to use a self-signed certificate to setup SSL, which is not yet supported by Mattermost. You can resolve this issue by setting up a load balancer like Ngnix. A ticket exists to [add support for self-signed certificates in future](x509: certificate signed by unknown authority).
diff --git a/model/message.go b/model/message.go
index 122af4d9c..2725353ac 100644
--- a/model/message.go
+++ b/model/message.go
@@ -9,14 +9,14 @@ import (
)
const (
- ACTION_TYPING = "typing"
- ACTION_POSTED = "posted"
- ACTION_POST_EDITED = "post_edited"
- ACTION_POST_DELETED = "post_deleted"
- ACTION_VIEWED = "viewed"
- ACTION_NEW_USER = "new_user"
- ACTION_USER_ADDED = "user_added"
- ACTION_USER_REMOVED = "user_removed"
+ ACTION_TYPING = "typing"
+ ACTION_POSTED = "posted"
+ ACTION_POST_EDITED = "post_edited"
+ ACTION_POST_DELETED = "post_deleted"
+ ACTION_CHANNEL_VIEWED = "channel_viewed"
+ ACTION_NEW_USER = "new_user"
+ ACTION_USER_ADDED = "user_added"
+ ACTION_USER_REMOVED = "user_removed"
)
type Message struct {
diff --git a/utils/mail.go b/utils/mail.go
index c91c15a6a..07a79eeb2 100644
--- a/utils/mail.go
+++ b/utils/mail.go
@@ -6,15 +6,22 @@ package utils
import (
l4g "code.google.com/p/log4go"
"crypto/tls"
+ "encoding/base64"
"fmt"
"github.com/mattermost/platform/model"
- "html"
"net"
"net/mail"
"net/smtp"
"time"
)
+func encodeRFC2047Word(s string) string {
+ // TODO: use `mime.BEncoding.Encode` instead when `go` >= 1.5
+ // return mime.BEncoding.Encode("utf-8", s)
+ dst := base64.StdEncoding.EncodeToString([]byte(s))
+ return "=?utf-8?b?" + dst + "?="
+}
+
func connectToSMTPServer(config *model.Config) (net.Conn, *model.AppError) {
var conn net.Conn
var err error
@@ -102,9 +109,10 @@ func SendMailUsingConfig(to, subject, body string, config *model.Config) *model.
headers := make(map[string]string)
headers["From"] = fromMail.String()
headers["To"] = toMail.String()
- headers["Subject"] = html.UnescapeString(subject)
+ headers["Subject"] = encodeRFC2047Word(subject)
headers["MIME-version"] = "1.0"
- headers["Content-Type"] = "text/html"
+ headers["Content-Type"] = "text/html; charset=\"utf-8\""
+ headers["Content-Transfer-Encoding"] = "8bit"
headers["Date"] = time.Now().Format(time.RFC1123Z)
message := ""
diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx
index 74d6c64e3..2c944913f 100644
--- a/web/react/components/activity_log_modal.jsx
+++ b/web/react/components/activity_log_modal.jsx
@@ -81,6 +81,7 @@ export default class ActivityLogModal extends React.Component {
const currentSession = this.state.sessions[i];
const lastAccessTime = new Date(currentSession.last_activity_at);
const firstAccessTime = new Date(currentSession.create_at);
+ let devicePlatform = currentSession.props.platform;
let devicePicture = '';
if (currentSession.props.platform === 'Windows') {
@@ -88,7 +89,12 @@ export default class ActivityLogModal extends React.Component {
} else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') {
devicePicture = 'fa fa-apple';
} else if (currentSession.props.platform === 'Linux') {
- devicePicture = 'fa fa-linux';
+ if (currentSession.props.os.indexOf('Android') >= 0) {
+ devicePlatform = 'Android';
+ devicePicture = 'fa fa-android';
+ } else {
+ devicePicture = 'fa fa-linux';
+ }
}
let moreInfo;
@@ -119,7 +125,7 @@ export default class ActivityLogModal extends React.Component {
className='activity-log__table'
>
<div className='activity-log__report'>
- <div className='report__platform'><i className={devicePicture} />{currentSession.props.platform}</div>
+ <div className='report__platform'><i className={devicePicture} />{devicePlatform}</div>
<div className='report__info'>
<div>{`Last activity: ${lastAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}</div>
{moreInfo}
diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx
index f2fb8ac78..f770d166c 100644
--- a/web/react/components/admin_console/admin_controller.jsx
+++ b/web/react/components/admin_console/admin_controller.jsx
@@ -40,9 +40,13 @@ export default class AdminController extends React.Component {
config: AdminStore.getConfig(),
teams: AdminStore.getAllTeams(),
selectedTeams,
- selected: 'service_settings',
- selectedTeam: null
+ selected: props.tab || 'service_settings',
+ selectedTeam: props.teamId || null
};
+
+ if (!props.tab) {
+ history.replaceState(null, null, `/admin_console/${this.state.selected}`);
+ }
}
componentDidMount() {
@@ -142,7 +146,9 @@ export default class AdminController extends React.Component {
} else if (this.state.selected === 'service_settings') {
tab = <ServiceSettingsTab config={this.state.config} />;
} else if (this.state.selected === 'team_users') {
- tab = <TeamUsersTab team={this.state.teams[this.state.selectedTeam]} />;
+ if (this.state.teams) {
+ tab = <TeamUsersTab team={this.state.teams[this.state.selectedTeam]} />;
+ }
}
}
diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx
index 4c2a473b6..b0e01ff17 100644
--- a/web/react/components/admin_console/admin_sidebar.jsx
+++ b/web/react/components/admin_console/admin_sidebar.jsx
@@ -24,6 +24,7 @@ export default class AdminSidebar extends React.Component {
handleClick(name, teamId, e) {
e.preventDefault();
this.props.selectTab(name, teamId);
+ history.pushState({name: name, teamId: teamId}, null, `/admin_console/${name}/${teamId || ''}`);
}
isSelected(name, teamId) {
@@ -53,6 +54,9 @@ export default class AdminSidebar extends React.Component {
}
componentDidMount() {
+ if ($(window).width() > 768) {
+ $('.nav-pills__container').perfectScrollbar();
+ }
}
showTeamSelect(e) {
diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx
index 01759b222..40e00ff04 100644
--- a/web/react/components/admin_console/email_settings.jsx
+++ b/web/react/components/admin_console/email_settings.jsx
@@ -440,9 +440,11 @@ export default class EmailSettings extends React.Component {
className='table table-bordered'
cellPadding='5'
>
- <tr><td className='help-text'>{'None'}</td><td className='help-text'>{'Mattermost will send email over an unsecure connection.'}</td></tr>
- <tr><td className='help-text'>{'TLS'}</td><td className='help-text'>{'Encrypts the communication between Mattermost and your email server.'}</td></tr>
- <tr><td className='help-text'>{'STARTTLS'}</td><td className='help-text'>{'Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'}</td></tr>
+ <tbody>
+ <tr><td className='help-text'>{'None'}</td><td className='help-text'>{'Mattermost will send email over an unsecure connection.'}</td></tr>
+ <tr><td className='help-text'>{'TLS'}</td><td className='help-text'>{'Encrypts the communication between Mattermost and your email server.'}</td></tr>
+ <tr><td className='help-text'>{'STARTTLS'}</td><td className='help-text'>{'Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'}</td></tr>
+ </tbody>
</table>
</div>
<div className='help-text'>
diff --git a/web/react/components/admin_console/log_settings.jsx b/web/react/components/admin_console/log_settings.jsx
index 931818bb8..7e9eda89b 100644
--- a/web/react/components/admin_console/log_settings.jsx
+++ b/web/react/components/admin_console/log_settings.jsx
@@ -249,22 +249,24 @@ export default class LogSettings extends React.Component {
onChange={this.handleChange}
disabled={!this.state.fileEnable}
/>
- <p className='help-text'>
+ <div className='help-text'>
{'Format of log message output. If blank will be set to "[%D %T] [%L] %M", where:'}
<div className='help-text'>
<table
className='table table-bordered'
cellPadding='5'
>
- <tr><td className='help-text'>{'%T'}</td><td className='help-text'>{'Time (15:04:05 MST)'}</td></tr>
- <tr><td className='help-text'>{'%D'}</td><td className='help-text'>{'Date (2006/01/02)'}</td></tr>
- <tr><td className='help-text'>{'%d'}</td><td className='help-text'>{'Date (01/02/06)'}</td></tr>
- <tr><td className='help-text'>{'%L'}</td><td className='help-text'>{'Level (DEBG, INFO, EROR)'}</td></tr>
- <tr><td className='help-text'>{'%S'}</td><td className='help-text'>{'Source'}</td></tr>
- <tr><td className='help-text'>{'%M'}</td><td className='help-text'>{'Message'}</td></tr>
+ <tbody>
+ <tr><td className='help-text'>{'%T'}</td><td className='help-text'>{'Time (15:04:05 MST)'}</td></tr>
+ <tr><td className='help-text'>{'%D'}</td><td className='help-text'>{'Date (2006/01/02)'}</td></tr>
+ <tr><td className='help-text'>{'%d'}</td><td className='help-text'>{'Date (01/02/06)'}</td></tr>
+ <tr><td className='help-text'>{'%L'}</td><td className='help-text'>{'Level (DEBG, INFO, EROR)'}</td></tr>
+ <tr><td className='help-text'>{'%S'}</td><td className='help-text'>{'Source'}</td></tr>
+ <tr><td className='help-text'>{'%M'}</td><td className='help-text'>{'Message'}</td></tr>
+ </tbody>
</table>
</div>
- </p>
+ </div>
</div>
</div>
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index 7582de6c4..1b709336f 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -4,7 +4,6 @@
const ChannelStore = require('../stores/channel_store.jsx');
const UserStore = require('../stores/user_store.jsx');
const PostStore = require('../stores/post_store.jsx');
-const SocketStore = require('../stores/socket_store.jsx');
const NavbarSearchBox = require('./search_bar.jsx');
const AsyncClient = require('../utils/async_client.jsx');
const Client = require('../utils/client.jsx');
@@ -25,7 +24,6 @@ export default class ChannelHeader extends React.Component {
super(props);
this.onListenerChange = this.onListenerChange.bind(this);
- this.onSocketChange = this.onSocketChange.bind(this);
this.handleLeave = this.handleLeave.bind(this);
this.searchMentions = this.searchMentions.bind(this);
@@ -45,7 +43,6 @@ export default class ChannelHeader extends React.Component {
ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
PostStore.addSearchChangeListener(this.onListenerChange);
UserStore.addChangeListener(this.onListenerChange);
- SocketStore.addChangeListener(this.onSocketChange);
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onListenerChange);
@@ -60,16 +57,9 @@ export default class ChannelHeader extends React.Component {
}
$('.channel-header__info .description').popover({placement: 'bottom', trigger: 'hover', html: true, delay: {show: 500, hide: 500}});
}
- onSocketChange(msg) {
- if (msg.action === 'new_user' ||
- msg.action === 'user_added' ||
- (msg.action === 'user_removed' && msg.user_id !== UserStore.getCurrentId())) {
- AsyncClient.getChannelExtraInfo(true);
- }
- }
handleLeave() {
Client.leaveChannel(this.state.channel.id,
- function handleLeaveSuccess() {
+ () => {
AppDispatcher.handleViewAction({
type: ActionTypes.LEAVE_CHANNEL,
id: this.state.channel.id
@@ -77,8 +67,8 @@ export default class ChannelHeader extends React.Component {
const townsquare = ChannelStore.getByName('town-square');
Utils.switchChannel(townsquare);
- }.bind(this),
- function handleLeaveError(err) {
+ },
+ (err) => {
AsyncClient.dispatchError(err, 'handleLeave');
}
);
diff --git a/web/react/components/edit_post_modal.jsx b/web/react/components/edit_post_modal.jsx
index 90d9696e7..b259b3c18 100644
--- a/web/react/components/edit_post_modal.jsx
+++ b/web/react/components/edit_post_modal.jsx
@@ -70,7 +70,7 @@ export default class EditPostModal extends React.Component {
refocusId: options.refocusId || ''
});
- $(React.findDOMNode(this.refs.modal)).modal('show');
+ $(ReactDOM.findDOMNode(this.refs.modal)).modal('show');
}
componentDidMount() {
var self = this;
@@ -92,7 +92,7 @@ export default class EditPostModal extends React.Component {
$('#edit_textbox').get(0).focus();
});
- $(React.findDOMNode(this.refs.modal)).on('hide.bs.modal', function onShown() {
+ $(ReactDOM.findDOMNode(this.refs.modal)).on('hide.bs.modal', function onShown() {
if (self.state.refocusId !== '') {
setTimeout(() => {
$(self.state.refocusId).get(0).focus();
diff --git a/web/react/components/more_channels.jsx b/web/react/components/more_channels.jsx
index a20c5cad5..a0084ad30 100644
--- a/web/react/components/more_channels.jsx
+++ b/web/react/components/more_channels.jsx
@@ -83,7 +83,7 @@ export default class MoreChannels extends React.Component {
moreChannels = <LoadingScreen />;
} else if (channels.length) {
moreChannels = (
- <table className='more-channel-table table'>
+ <table className='more-table table'>
<tbody>
{channels.map(function cMap(channel, index) {
var joinButton;
@@ -108,8 +108,8 @@ export default class MoreChannels extends React.Component {
return (
<tr key={channel.id}>
<td>
- <p className='more-channel-name'>{channel.display_name}</p>
- <p className='more-channel-description'>{channel.description}</p>
+ <p className='more-name'>{channel.display_name}</p>
+ <p className='more-description'>{channel.description}</p>
</td>
<td className='td--action'>
{joinButton}
diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx
index 08b64de8b..105199035 100644
--- a/web/react/components/more_direct_channels.jsx
+++ b/web/react/components/more_direct_channels.jsx
@@ -140,12 +140,12 @@ export default class MoreDirectChannels extends React.Component {
if (user.nickname) {
const separator = fullName ? ' - ' : '';
details.push(
- <span
+ <p
key={`${user.nickname}__nickname`}
- className='nickname'
+ className='more-description'
>
{separator + user.nickname}
- </span>
+ </p>
);
}
@@ -170,31 +170,38 @@ export default class MoreDirectChannels extends React.Component {
}
return (
- <li
- key={user.id}
- className='direct-channel'
- >
- <div className='col-xs-1 image-div'>
+ <tr>
+ <td
+ key={user.id}
+ className='direct-channel'
+ >
<img
- className='profile-image'
+ className='profile-img pull-left'
+ width='38'
+ height='38'
src={`/api/v1/users/${user.id}/image?time=${user.update_at}`}
/>
- </div>
- <div className='col-xs-9'>
- <div className='username'>
+ <div className='more-name'>
{user.username}
</div>
- <div>
- {details}
- </div>
- </div>
- <div className='col-xs-2 btn-div'>
+ {details}
+ </td>
+ <td className='td--action lg'>
{joinButton}
- </div>
- </li>
+ </td>
+ </tr>
);
}
+ componentDidUpdate(prevProps) {
+ if (!prevProps.show && this.props.show) {
+ $(ReactDOM.findDOMNode(this.refs.userList)).css('max-height', $(window).height() - 300);
+ if ($(window).width() > 768) {
+ $(ReactDOM.findDOMNode(this.refs.userList)).perfectScrollbar();
+ }
+ }
+ }
+
render() {
if (!this.props.show) {
return null;
@@ -213,7 +220,7 @@ export default class MoreDirectChannels extends React.Component {
const userEntries = users.map(this.createRowForUser);
if (userEntries.length === 0) {
- userEntries.push(<li key='no-users-found'>{'No users found :('}</li>);
+ userEntries.push(<tr key='no-users-found'><td>{'No users found :('}</td></tr>);
}
let memberString = 'Member';
@@ -232,26 +239,35 @@ export default class MoreDirectChannels extends React.Component {
<Modal
className='modal-direct-channels'
show={this.props.show}
- bsSize='large'
onHide={this.handleHide}
>
<Modal.Header closeButton={true}>
- <Modal.Title>{'More Direct Messages'}</Modal.Title>
+ <Modal.Title>{'Team Directory'}</Modal.Title>
</Modal.Header>
<Modal.Body>
- <div>
- <input
- ref='filter'
- className='form-control filter-textbox'
- placeholder='Search members'
- onInput={this.handleFilterChange}
- style={{width: '200px', display: 'inline'}}
- />
- <span className='member-count pull-right'>{count}</span>
+ <div className='row filter-row'>
+ <div className='col-sm-6'>
+ <input
+ ref='filter'
+ className='form-control filter-textbox'
+ placeholder='Search members'
+ onInput={this.handleFilterChange}
+ />
+ </div>
+ <div className='col-sm-6'>
+ <span className='member-count'>{count}</span>
+ </div>
+ </div>
+ <div
+ ref='userList'
+ className='user-list'
+ >
+ <table className='more-table table'>
+ <tbody>
+ {userEntries}
+ </tbody>
+ </table>
</div>
- <ul className='user-list'>
- {userEntries}
- </ul>
</Modal.Body>
<Modal.Footer>
<button
diff --git a/web/react/components/msg_typing.jsx b/web/react/components/msg_typing.jsx
index 569942390..1bd23c55c 100644
--- a/web/react/components/msg_typing.jsx
+++ b/web/react/components/msg_typing.jsx
@@ -1,8 +1,11 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-var SocketStore = require('../stores/socket_store.jsx');
-var UserStore = require('../stores/user_store.jsx');
+const SocketStore = require('../stores/socket_store.jsx');
+const UserStore = require('../stores/user_store.jsx');
+
+const Constants = require('../utils/constants.jsx');
+const SocketEvents = Constants.SocketEvents;
export default class MsgTyping extends React.Component {
constructor(props) {
@@ -33,9 +36,9 @@ export default class MsgTyping extends React.Component {
}
onChange(msg) {
- if (msg.action === 'typing' &&
- this.props.channelId === msg.channel_id &&
- this.props.parentId === msg.props.parent_id) {
+ if (msg.action === SocketEvents.TYPING &&
+ this.props.channelId === msg.channel_id &&
+ this.props.parentId === msg.props.parent_id) {
this.lastTime = new Date().getTime();
var username = 'Someone';
@@ -52,7 +55,7 @@ export default class MsgTyping extends React.Component {
}
}.bind(this), 3000);
}
- } else if (msg.action === 'posted' && msg.channel_id === this.props.channelId) {
+ } else if (msg.action === SocketEvents.POSTED && msg.channel_id === this.props.channelId) {
this.setState({text: ''});
}
}
diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx
index 16ae693fa..155e88600 100644
--- a/web/react/components/popover_list_members.jsx
+++ b/web/react/components/popover_list_members.jsx
@@ -35,13 +35,20 @@ export default class PopoverListMembers extends React.Component {
const teamMembers = UserStore.getProfilesUsernameMap();
if (members && teamMembers) {
- members.sort(function compareByLocal(a, b) {
+ members.sort((a, b) => {
return a.username.localeCompare(b.username);
});
- members.forEach(function addMemberElement(m) {
+ members.forEach((m, i) => {
if (teamMembers[m.username] && teamMembers[m.username].delete_at <= 0) {
- popoverHtml.push(<div className='text--nowrap'>{m.username}</div>);
+ popoverHtml.push(
+ <div
+ className='text--nowrap'
+ key={'popover-member-' + i}
+ >
+ {m.username}
+ </div>
+ );
count++;
}
});
@@ -57,8 +64,15 @@ export default class PopoverListMembers extends React.Component {
<OverlayTrigger
trigger='click'
placement='bottom'
- rootClose='true'
- overlay={<Popover title='Members'>{popoverHtml}</Popover>}
+ rootClose={true}
+ overlay={
+ <Popover
+ title='Members'
+ id='member-list-popover'
+ >
+ {popoverHtml}
+ </Popover>
+ }
>
<div id='member_popover'>
<div>
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index 3a82ff9bc..ae94bd42e 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -116,7 +116,7 @@ export default class PostBody extends React.Component {
}
var metadata = data.items[0].snippet;
this.receivedYoutubeData = true;
- this.setState({youtubeUploader: metadata.channelTitle, youtubeTitle: metadata.title});
+ this.setState({youtubeTitle: metadata.title});
}
if (global.window.mm_config.GoogleDeveloperKey && !this.receivedYoutubeData) {
@@ -134,18 +134,12 @@ export default class PostBody extends React.Component {
header = header + ' - ';
}
- let uploader = this.state.youtubeUploader;
- if (!uploader) {
- uploader = 'unknown';
- }
-
return (
<div className='post-comment'>
<h4>
<span className='video-type'>{header}</span>
<span className='video-title'><a href={link}>{this.state.youtubeTitle}</a></span>
</h4>
- <h4 className='video-uploader'>{uploader}</h4>
<div
className='video-div embed-responsive-item'
id={youtubeId}
diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx
index a95095ff6..36260d77c 100644
--- a/web/react/components/post_info.jsx
+++ b/web/react/components/post_info.jsx
@@ -150,7 +150,7 @@ export default class PostInfo extends React.Component {
<ul className='post-header post-info'>
<li className='post-header-col'>
<OverlayTrigger
- delayShow='500'
+ delayShow={500}
container={this}
placement='top'
overlay={tooltip}
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index 29728d368..4402745e1 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -1,20 +1,24 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-var PostStore = require('../stores/post_store.jsx');
-var ChannelStore = require('../stores/channel_store.jsx');
-var UserStore = require('../stores/user_store.jsx');
-var PreferenceStore = require('../stores/preference_store.jsx');
-var UserProfile = require('./user_profile.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var Post = require('./post.jsx');
-var LoadingScreen = require('./loading_screen.jsx');
-var SocketStore = require('../stores/socket_store.jsx');
-var utils = require('../utils/utils.jsx');
-var Client = require('../utils/client.jsx');
-var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var Constants = require('../utils/constants.jsx');
-var ActionTypes = Constants.ActionTypes;
+const Post = require('./post.jsx');
+const UserProfile = require('./user_profile.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+const LoadingScreen = require('./loading_screen.jsx');
+
+const PostStore = require('../stores/post_store.jsx');
+const ChannelStore = require('../stores/channel_store.jsx');
+const UserStore = require('../stores/user_store.jsx');
+const SocketStore = require('../stores/socket_store.jsx');
+const PreferenceStore = require('../stores/preference_store.jsx');
+
+const utils = require('../utils/utils.jsx');
+const Client = require('../utils/client.jsx');
+const Constants = require('../utils/constants.jsx');
+const ActionTypes = Constants.ActionTypes;
+const SocketEvents = Constants.SocketEvents;
+
+const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
export default class PostList extends React.Component {
constructor(props) {
@@ -58,7 +62,7 @@ export default class PostList extends React.Component {
}
}
- postList.order.sort(function postSort(a, b) {
+ postList.order.sort((a, b) => {
if (postList.posts[a].create_at > postList.posts[b].create_at) {
return -1;
}
@@ -82,7 +86,7 @@ export default class PostList extends React.Component {
}
return {
- postList: postList
+ postList
};
}
componentDidMount() {
@@ -263,14 +267,14 @@ export default class PostList extends React.Component {
Client.getPosts(
id,
PostStore.getLatestUpdate(id),
- function success() {
+ () => {
this.loadInProgress = false;
this.setState({isFirstLoadComplete: true});
- }.bind(this),
- function fail() {
+ },
+ () => {
this.loadInProgress = false;
this.setState({isFirstLoadComplete: true});
- }.bind(this)
+ }
);
}
onChange() {
@@ -281,28 +285,16 @@ export default class PostList extends React.Component {
}
}
onSocketChange(msg) {
- var post;
- if (msg.action === 'posted' || msg.action === 'post_edited') {
- post = JSON.parse(msg.props.post);
- PostStore.storePost(post);
- } else if (msg.action === 'post_deleted') {
+ if (msg.action === SocketEvents.POST_DELETED) {
var activeRoot = $(document.activeElement).closest('.comment-create-body')[0];
var activeRootPostId = '';
if (activeRoot && activeRoot.id.length > 0) {
activeRootPostId = activeRoot.id;
}
- post = JSON.parse(msg.props.post);
-
- PostStore.storeUnseenDeletedPost(post);
- PostStore.removePost(post, true);
- PostStore.emitChange();
-
if (activeRootPostId === msg.props.post_id && UserStore.getCurrentId() !== msg.user_id) {
$('#post_deleted').modal('show');
}
- } else if (msg.action === 'new_user') {
- AsyncClient.getProfiles();
}
}
onTimeChange() {
@@ -352,7 +344,7 @@ export default class PostList extends React.Component {
data-title={channel.display_name}
data-channelid={channel.id}
>
- <i className='fa fa-pencil'></i>Set a description
+ <i className='fa fa-pencil'></i>{'Set a description'}
</a>
</div>
);
diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx
index 402e64080..d3a4cfaeb 100644
--- a/web/react/components/rhs_comment.jsx
+++ b/web/react/components/rhs_comment.jsx
@@ -29,7 +29,7 @@ export default class RhsComment extends React.Component {
var post = this.props.post;
Client.createPost(post, post.channel_id,
- function success(data) {
+ (data) => {
AsyncClient.getPosts(post.channel_id);
var channel = ChannelStore.get(post.channel_id);
@@ -43,11 +43,11 @@ export default class RhsComment extends React.Component {
post: data
});
},
- function fail() {
+ () => {
post.state = Constants.POST_FAILED;
PostStore.updatePendingPost(post);
this.forceUpdate();
- }.bind(this)
+ }
);
post.state = Constants.POST_LOADING;
@@ -84,7 +84,10 @@ export default class RhsComment extends React.Component {
if (isOwner) {
dropdownContents.push(
- <li role='presentation'>
+ <li
+ role='presentation'
+ key='edit-button'
+ >
<a
href='#'
role='menuitem'
@@ -95,7 +98,7 @@ export default class RhsComment extends React.Component {
data-postid={post.id}
data-channelid={post.channel_id}
>
- Edit
+ {'Edit'}
</a>
</li>
);
@@ -103,7 +106,10 @@ export default class RhsComment extends React.Component {
if (isOwner || isAdmin) {
dropdownContents.push(
- <li role='presentation'>
+ <li
+ role='presentation'
+ key='delete-button'
+ >
<a
href='#'
role='menuitem'
@@ -114,7 +120,7 @@ export default class RhsComment extends React.Component {
data-channelid={post.channel_id}
data-comments={0}
>
- Delete
+ {'Delete'}
</a>
</li>
);
@@ -162,7 +168,7 @@ export default class RhsComment extends React.Component {
href='#'
onClick={this.retryComment}
>
- Retry
+ {'Retry'}
</a>
);
} else if (post.state === Constants.POST_LOADING) {
@@ -213,14 +219,14 @@ export default class RhsComment extends React.Component {
</li>
</ul>
<div className='post-body'>
- <p className={postClass}>
+ <div className={postClass}>
{loading}
<div
ref='message_holder'
onClick={TextFormatting.handleClick}
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(post.message)}}
/>
- </p>
+ </div>
{fileAttachment}
</div>
</div>
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 2c98db7f3..14868985b 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -2,7 +2,6 @@
// See License.txt for license information.
const AsyncClient = require('../utils/async_client.jsx');
-const BrowserStore = require('../stores/browser_store.jsx');
const ChannelStore = require('../stores/channel_store.jsx');
const Client = require('../utils/client.jsx');
const Constants = require('../utils/constants.jsx');
@@ -11,7 +10,6 @@ const NewChannelFlow = require('./new_channel_flow.jsx');
const MoreDirectChannels = require('./more_direct_channels.jsx');
const SearchBox = require('./search_bar.jsx');
const SidebarHeader = require('./sidebar_header.jsx');
-const SocketStore = require('../stores/socket_store.jsx');
const TeamStore = require('../stores/team_store.jsx');
const UnreadChannelIndicator = require('./unread_channel_indicator.jsx');
const UserStore = require('../stores/user_store.jsx');
@@ -46,7 +44,7 @@ export default class Sidebar extends React.Component {
const state = this.getStateFromStores();
state.newChannelModalType = '';
- state.showMoreDirectChannelsModal = false;
+ state.showDirectChannelsModal = false;
state.loadingDMChannel = -1;
this.state = state;
@@ -129,10 +127,11 @@ export default class Sidebar extends React.Component {
UserStore.addChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onChange);
TeamStore.addChangeListener(this.onChange);
- SocketStore.addChangeListener(this.onSocketChange);
PreferenceStore.addChangeListener(this.onChange);
- $('.nav-pills__container').perfectScrollbar();
+ if ($(window).width() > 768) {
+ $('.nav-pills__container').perfectScrollbar();
+ }
this.updateTitle();
this.updateUnreadIndicators();
@@ -160,7 +159,6 @@ export default class Sidebar extends React.Component {
UserStore.removeChangeListener(this.onChange);
UserStore.removeStatusesChangeListener(this.onChange);
TeamStore.removeChangeListener(this.onChange);
- SocketStore.removeChangeListener(this.onSocketChange);
PreferenceStore.removeChangeListener(this.onChange);
}
onChange() {
@@ -169,94 +167,6 @@ export default class Sidebar extends React.Component {
this.setState(newState);
}
}
- onSocketChange(msg) {
- if (msg.action === 'posted') {
- if (ChannelStore.getCurrentId() === msg.channel_id) {
- if (window.isActive) {
- AsyncClient.updateLastViewedAt();
- }
- } else {
- AsyncClient.getChannels();
- }
-
- if (UserStore.getCurrentId() !== msg.user_id) {
- var mentions = [];
- if (msg.props.mentions) {
- mentions = JSON.parse(msg.props.mentions);
- }
- var channel = ChannelStore.get(msg.channel_id);
-
- const user = UserStore.getCurrentUser();
- const member = ChannelStore.getMember(msg.channel_id);
-
- var notifyLevel = member && member.notify_props ? member.notify_props.desktop : 'default';
- if (notifyLevel === 'default') {
- notifyLevel = user.notify_props.desktop;
- }
-
- if (notifyLevel === 'none') {
- return;
- } else if (notifyLevel === 'mention' && mentions.indexOf(user.id) === -1 && channel.type !== 'D') {
- return;
- }
-
- var username = 'Someone';
- if (UserStore.hasProfile(msg.user_id)) {
- username = UserStore.getProfile(msg.user_id).username;
- }
-
- var title = 'Posted';
- if (channel) {
- title = channel.display_name;
- }
-
- var repRegex = new RegExp('<br>', 'g');
- var post = JSON.parse(msg.props.post);
- var msgProps = msg.props;
- var notifyText = post.message.replace(repRegex, '\n').replace(/\n+/g, ' ').replace('<mention>', '').replace('</mention>', '');
-
- if (notifyText.length > 50) {
- notifyText = notifyText.substring(0, 49) + '...';
- }
-
- if (notifyText.length === 0) {
- if (msgProps.image) {
- Utils.notifyMe(title, username + ' uploaded an image', channel);
- } else if (msgProps.otherFile) {
- Utils.notifyMe(title, username + ' uploaded a file', channel);
- } else {
- Utils.notifyMe(title, username + ' did something new', channel);
- }
- } else {
- Utils.notifyMe(title, username + ' wrote: ' + notifyText, channel);
- }
- if (!user.notify_props || user.notify_props.desktop_sound === 'true') {
- Utils.ding();
- }
- }
- } else if (msg.action === 'viewed') {
- if (ChannelStore.getCurrentId() !== msg.channel_id && UserStore.getCurrentId() === msg.user_id) {
- AsyncClient.getChannel(msg.channel_id);
- }
- } else if (msg.action === 'user_added') {
- if (UserStore.getCurrentId() === msg.user_id) {
- AsyncClient.getChannel(msg.channel_id);
- }
- } else if (msg.action === 'user_removed') {
- if (msg.user_id === UserStore.getCurrentId()) {
- AsyncClient.getChannels(true);
-
- if (msg.props.remover !== msg.user_id && msg.props.channel_id === ChannelStore.getCurrentId() && $('#removed_from_channel').length > 0) {
- var sentState = {};
- sentState.channelName = ChannelStore.getCurrent().display_name;
- sentState.remover = UserStore.getProfile(msg.props.remover).username;
-
- BrowserStore.setItem('channel-removed-state', sentState);
- $('#removed_from_channel').modal('show');
- }
- }
- }
- }
updateTitle() {
const channel = ChannelStore.getCurrent();
if (channel) {
@@ -471,11 +381,13 @@ export default class Sidebar extends React.Component {
}
let closeButton = null;
- const removeTooltip = <Tooltip>{'Remove from list'}</Tooltip>;
+ const removeTooltip = (
+ <Tooltip id='remove-dm-tooltip'>{'Remove from list'}</Tooltip>
+ );
if (handleClose && !badge) {
closeButton = (
<OverlayTrigger
- delayShow='1000'
+ delayShow={1000}
placement='top'
overlay={removeTooltip}
>
@@ -564,8 +476,12 @@ export default class Sidebar extends React.Component {
showChannelModal = true;
}
- const createChannelTootlip = <Tooltip>{'Create new channel'}</Tooltip>;
- const createGroupTootlip = <Tooltip>{'Create new group'}</Tooltip>;
+ const createChannelTootlip = (
+ <Tooltip id='new-channel-tooltip' >{'Create new channel'}</Tooltip>
+ );
+ const createGroupTootlip = (
+ <Tooltip id='new-group-tooltip'>{'Create new group'}</Tooltip>
+ );
return (
<div>
@@ -607,7 +523,7 @@ export default class Sidebar extends React.Component {
<h4>
{'Channels'}
<OverlayTrigger
- delayShow='500'
+ delayShow={500}
placement='top'
overlay={createChannelTootlip}
>
@@ -640,7 +556,7 @@ export default class Sidebar extends React.Component {
<h4>
{'Private Groups'}
<OverlayTrigger
- delayShow='500'
+ delayShow={500}
placement='top'
overlay={createGroupTootlip}
>
diff --git a/web/react/components/team_signup_username_page.jsx b/web/react/components/team_signup_username_page.jsx
index fa8a031a0..21e76e2b8 100644
--- a/web/react/components/team_signup_username_page.jsx
+++ b/web/react/components/team_signup_username_page.jsx
@@ -15,7 +15,12 @@ export default class TeamSignupUsernamePage extends React.Component {
}
submitBack(e) {
e.preventDefault();
- this.props.state.wizard = 'send_invites';
+ if (global.window.config.SendEmailNotifications === 'true') {
+ this.props.state.wizard = 'send_invites';
+ } else {
+ this.props.state.wizard = 'team_url';
+ }
+
this.props.updateParent(this.props.state);
}
submitNext(e) {
diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx
index e731fb92c..4a759bb21 100644
--- a/web/react/components/user_profile.jsx
+++ b/web/react/components/user_profile.jsx
@@ -65,22 +65,33 @@ export default class UserProfile extends React.Component {
var dataContent = [];
dataContent.push(
- <img className='user-popover__image'
+ <img
+ className='user-popover__image'
src={'/api/v1/users/' + this.state.profile.id + '/image?time=' + this.state.profile.update_at}
height='128'
width='128'
+ key='user-popover-image'
/>
);
+
if (!global.window.mm_config.ShowEmailAddress === 'true') {
- dataContent.push(<div className='text-nowrap'>{'Email not shared'}</div>);
+ dataContent.push(
+ <div
+ className='text-nowrap'
+ key='user-popover-no-email'
+ >
+ {'Email not shared'}
+ </div>
+ );
} else {
dataContent.push(
<div
data-toggle='tooltip'
- title="' + this.state.profile.email + '"
+ title={this.state.profile.email}
+ key='user-popover-email'
>
<a
- href="mailto:' + this.state.profile.email + '"
+ href={'mailto:' + this.state.profile.email}
className='text-nowrap text-lowercase user-popover__email'
>
{this.state.profile.email}
@@ -93,15 +104,22 @@ export default class UserProfile extends React.Component {
<OverlayTrigger
trigger='click'
placement='right'
- rootClose='true'
- overlay={<Popover title={this.state.profile.username}>{dataContent}</Popover>}
- >
- <div
- className='user-popover'
- id={'profile_' + this.uniqueId}
+ rootClose={true}
+ overlay={
+ <Popover
+ title={this.state.profile.username}
+ id='user-profile-popover'
+ >
+ {dataContent}
+ </Popover>
+ }
>
- {name}
- </div>
+ <div
+ className='user-popover'
+ id={'profile_' + this.uniqueId}
+ >
+ {name}
+ </div>
</OverlayTrigger>
);
}
diff --git a/web/react/components/user_settings/user_settings_appearance.jsx b/web/react/components/user_settings/user_settings_appearance.jsx
index 7f363e92e..8c62a189d 100644
--- a/web/react/components/user_settings/user_settings_appearance.jsx
+++ b/web/react/components/user_settings/user_settings_appearance.jsx
@@ -152,9 +152,8 @@ export default class UserSettingsAppearance extends React.Component {
<input type='radio'
checked={!displayCustom}
onChange={this.updateType.bind(this, 'premade')}
- >
- {'Theme Colors'}
- </input>
+ />
+ {'Theme Colors'}
</label>
<br/>
</div>
@@ -164,9 +163,8 @@ export default class UserSettingsAppearance extends React.Component {
<input type='radio'
checked={displayCustom}
onChange={this.updateType.bind(this, 'custom')}
- >
- {'Custom Theme'}
- </input>
+ />
+ {'Custom Theme'}
</label>
<br/>
</div>
diff --git a/web/react/components/user_settings/user_settings_display.jsx b/web/react/components/user_settings/user_settings_display.jsx
index ec209c218..22a62273c 100644
--- a/web/react/components/user_settings/user_settings_display.jsx
+++ b/web/react/components/user_settings/user_settings_display.jsx
@@ -1,7 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import { savePreferences } from '../../utils/client.jsx';
+import {savePreferences} from '../../utils/client.jsx';
import SettingItemMin from '../setting_item_min.jsx';
import SettingItemMax from '../setting_item_max.jsx';
import Constants from '../../utils/constants.jsx';
@@ -38,7 +38,7 @@ export default class UserSettingsDisplay extends React.Component {
);
}
handleClockRadio(militaryTime) {
- this.setState({militaryTime: militaryTime});
+ this.setState({militaryTime});
}
updateSection(section) {
this.setState(getDisplayStateFromStores());
@@ -57,7 +57,7 @@ export default class UserSettingsDisplay extends React.Component {
const serverError = this.state.serverError || null;
let clockSection;
if (this.props.activeSection === 'clock') {
- let clockFormat = [false, false];
+ const clockFormat = [false, false];
if (this.state.militaryTime === 'true') {
clockFormat[1] = true;
} else {
@@ -77,9 +77,8 @@ export default class UserSettingsDisplay extends React.Component {
type='radio'
checked={clockFormat[0]}
onChange={this.handleClockRadio.bind(this, 'false')}
- >
- 12-hour clock (example: 4:00 PM)
- </input>
+ />
+ {'12-hour clock (example: 4:00 PM)'}
</label>
<br/>
</div>
@@ -89,9 +88,8 @@ export default class UserSettingsDisplay extends React.Component {
type='radio'
checked={clockFormat[1]}
onChange={this.handleClockRadio.bind(this, 'true')}
- >
- 24-hour clock (example: 16:00)
- </input>
+ />
+ {'24-hour clock (example: 16:00)'}
</label>
<br/>
</div>
@@ -99,7 +97,6 @@ export default class UserSettingsDisplay extends React.Component {
</div>
];
-
clockSection = (
<SettingItemMax
title='Clock Display'
@@ -138,13 +135,13 @@ export default class UserSettingsDisplay extends React.Component {
className='close'
data-dismiss='modal'
aria-label='Close'
- >
+ >
<span aria-hidden='true'>{'×'}</span>
</button>
<h4
className='modal-title'
ref='title'
- >
+ >
<i className='modal-back'></i>
{'Display Settings'}
</h4>
diff --git a/web/react/components/user_settings/user_settings_notifications.jsx b/web/react/components/user_settings/user_settings_notifications.jsx
index 62738efbd..61d49acb2 100644
--- a/web/react/components/user_settings/user_settings_notifications.jsx
+++ b/web/react/components/user_settings/user_settings_notifications.jsx
@@ -228,9 +228,8 @@ export default class NotificationsTab extends React.Component {
<input type='radio'
checked={notifyActive[0]}
onChange={this.handleNotifyRadio.bind(this, 'all')}
- >
- For all activity
- </input>
+ />
+ {'For all activity'}
</label>
<br/>
</div>
@@ -240,9 +239,8 @@ export default class NotificationsTab extends React.Component {
type='radio'
checked={notifyActive[1]}
onChange={this.handleNotifyRadio.bind(this, 'mention')}
- >
- Only for mentions and direct messages
- </input>
+ />
+ {'Only for mentions and direct messages'}
</label>
<br/>
</div>
@@ -252,9 +250,8 @@ export default class NotificationsTab extends React.Component {
type='radio'
checked={notifyActive[2]}
onChange={this.handleNotifyRadio.bind(this, 'none')}
- >
- Never
- </input>
+ />
+ {'Never'}
</label>
</div>
</div>
@@ -320,9 +317,8 @@ export default class NotificationsTab extends React.Component {
type='radio'
checked={soundActive[0]}
onChange={this.handleSoundRadio.bind(this, 'true')}
- >
- On
- </input>
+ />
+ {'On'}
</label>
<br/>
</div>
@@ -332,9 +328,8 @@ export default class NotificationsTab extends React.Component {
type='radio'
checked={soundActive[1]}
onChange={this.handleSoundRadio.bind(this, 'false')}
- >
- Off
- </input>
+ />
+ {'Off'}
</label>
<br/>
</div>
@@ -402,9 +397,8 @@ export default class NotificationsTab extends React.Component {
type='radio'
checked={emailActive[0]}
onChange={this.handleEmailRadio.bind(this, 'true')}
- >
- On
- </input>
+ />
+ {'On'}
</label>
<br/>
</div>
@@ -414,9 +408,8 @@ export default class NotificationsTab extends React.Component {
type='radio'
checked={emailActive[1]}
onChange={this.handleEmailRadio.bind(this, 'false')}
- >
- Off
- </input>
+ />
+ {'Off'}
</label>
<br/>
</div>
@@ -482,9 +475,8 @@ export default class NotificationsTab extends React.Component {
type='checkbox'
checked={this.state.firstNameKey}
onChange={handleUpdateFirstNameKey}
- >
- {'Your case sensitive first name "' + user.first_name + '"'}
- </input>
+ />
+ {'Your case sensitive first name "' + user.first_name + '"'}
</label>
</div>
</div>
@@ -502,9 +494,8 @@ export default class NotificationsTab extends React.Component {
type='checkbox'
checked={this.state.usernameKey}
onChange={handleUpdateUsernameKey}
- >
- {'Your non-case sensitive username "' + user.username + '"'}
- </input>
+ />
+ {'Your non-case sensitive username "' + user.username + '"'}
</label>
</div>
</div>
@@ -521,9 +512,8 @@ export default class NotificationsTab extends React.Component {
type='checkbox'
checked={this.state.mentionKey}
onChange={handleUpdateMentionKey}
- >
- {'Your username mentioned "@' + user.username + '"'}
- </input>
+ />
+ {'Your username mentioned "@' + user.username + '"'}
</label>
</div>
</div>
@@ -540,9 +530,8 @@ export default class NotificationsTab extends React.Component {
type='checkbox'
checked={this.state.allKey}
onChange={handleUpdateAllKey}
- >
- {'Team-wide mentions "@all"'}
- </input>
+ />
+ {'Team-wide mentions "@all"'}
</label>
</div>
</div>
@@ -559,9 +548,8 @@ export default class NotificationsTab extends React.Component {
type='checkbox'
checked={this.state.channelKey}
onChange={handleUpdateChannelKey}
- >
- {'Channel-wide mentions "@channel"'}
- </input>
+ />
+ {'Channel-wide mentions "@channel"'}
</label>
</div>
</div>
@@ -576,9 +564,8 @@ export default class NotificationsTab extends React.Component {
type='checkbox'
checked={this.state.customKeysChecked}
onChange={this.updateCustomMentionKeys}
- >
- {'Other non-case sensitive words, separated by commas:'}
- </input>
+ />
+ {'Other non-case sensitive words, separated by commas:'}
</label>
</div>
<input
diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx
index f75693470..322e68c17 100644
--- a/web/react/components/view_image.jsx
+++ b/web/react/components/view_image.jsx
@@ -6,6 +6,7 @@ const Utils = require('../utils/utils.jsx');
const Constants = require('../utils/constants.jsx');
const ViewImagePopoverBar = require('./view_image_popover_bar.jsx');
const Modal = ReactBootstrap.Modal;
+const KeyCodes = Constants.KeyCodes;
export default class ViewImageModal extends React.Component {
constructor(props) {
@@ -63,11 +64,11 @@ export default class ViewImageModal extends React.Component {
this.loadImage(id);
}
handleKeyPress(e) {
- if (!e) {
+ if (!e || !this.props.show) {
return;
- } else if (e.keyCode === 39) {
+ } else if (e.keyCode === KeyCodes.RIGHT) {
this.handleNext();
- } else if (e.keyCode === 37) {
+ } else if (e.keyCode === KeyCodes.LEFT) {
this.handlePrev();
}
}
diff --git a/web/react/pages/admin_console.jsx b/web/react/pages/admin_console.jsx
index c89cb4edc..ea9ae06f4 100644
--- a/web/react/pages/admin_console.jsx
+++ b/web/react/pages/admin_console.jsx
@@ -5,9 +5,12 @@ var ErrorBar = require('../components/error_bar.jsx');
var SelectTeamModal = require('../components/admin_console/select_team_modal.jsx');
var AdminController = require('../components/admin_console/admin_controller.jsx');
-export function setupAdminConsolePage() {
+export function setupAdminConsolePage(props) {
ReactDOM.render(
- <AdminController />,
+ <AdminController
+ tab={props.ActiveTab}
+ teamId={props.TeamId}
+ />,
document.getElementById('admin_controller')
);
diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx
index 77e7067ad..77951f214 100644
--- a/web/react/stores/socket_store.jsx
+++ b/web/react/stores/socket_store.jsx
@@ -1,15 +1,22 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var UserStore = require('./user_store.jsx');
-var ErrorStore = require('./error_store.jsx');
-var EventEmitter = require('events').EventEmitter;
+const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+const UserStore = require('./user_store.jsx');
+const PostStore = require('./post_store.jsx');
+const ChannelStore = require('./channel_store.jsx');
+const BrowserStore = require('./browser_store.jsx');
+const ErrorStore = require('./error_store.jsx');
+const EventEmitter = require('events').EventEmitter;
-var Constants = require('../utils/constants.jsx');
-var ActionTypes = Constants.ActionTypes;
+const Utils = require('../utils/utils.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
-var CHANGE_EVENT = 'change';
+const Constants = require('../utils/constants.jsx');
+const ActionTypes = Constants.ActionTypes;
+const SocketEvents = Constants.SocketEvents;
+
+const CHANGE_EVENT = 'change';
var conn;
@@ -94,6 +101,39 @@ class SocketStoreClass extends EventEmitter {
removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
}
+ handleMessage(msg) {
+ switch (msg.action) {
+ case SocketEvents.POSTED:
+ handleNewPostEvent(msg);
+ break;
+
+ case SocketEvents.POST_EDITED:
+ handlePostEditEvent(msg);
+ break;
+
+ case SocketEvents.POST_DELETED:
+ handlePostDeleteEvent(msg);
+ break;
+
+ case SocketEvents.NEW_USER:
+ handleNewUserEvent();
+ break;
+
+ case SocketEvents.USER_ADDED:
+ handleUserAddedEvent(msg);
+ break;
+
+ case SocketEvents.USER_REMOVED:
+ handleUserRemovedEvent(msg);
+ break;
+
+ case SocketEvents.CHANNEL_VIEWED:
+ handleChannelViewedEvent(msg);
+ break;
+
+ default:
+ }
+ }
sendMessage(msg) {
if (conn && conn.readyState === WebSocket.OPEN) {
conn.send(JSON.stringify(msg));
@@ -104,6 +144,138 @@ class SocketStoreClass extends EventEmitter {
}
}
+function handleNewPostEvent(msg) {
+ // Store post
+ const post = JSON.parse(msg.props.post);
+ PostStore.storePost(post);
+
+ // Update channel state
+ if (ChannelStore.getCurrentId() === msg.channel_id) {
+ if (window.isActive) {
+ AsyncClient.updateLastViewedAt();
+ }
+ } else {
+ AsyncClient.getChannel(msg.channel_id);
+ }
+
+ // Send desktop notification
+ if (UserStore.getCurrentId() !== msg.user_id) {
+ const msgProps = msg.props;
+
+ let mentions = [];
+ if (msgProps.mentions) {
+ mentions = JSON.parse(msg.props.mentions);
+ }
+
+ const channel = ChannelStore.get(msg.channel_id);
+ const user = UserStore.getCurrentUser();
+ const member = ChannelStore.getMember(msg.channel_id);
+
+ let notifyLevel = member && member.notify_props ? member.notify_props.desktop : 'default';
+ if (notifyLevel === 'default') {
+ notifyLevel = user.notify_props.desktop;
+ }
+
+ if (notifyLevel === 'none') {
+ return;
+ } else if (notifyLevel === 'mention' && mentions.indexOf(user.id) === -1 && channel.type !== 'D') {
+ return;
+ }
+
+ let username = 'Someone';
+ if (UserStore.hasProfile(msg.user_id)) {
+ username = UserStore.getProfile(msg.user_id).username;
+ }
+
+ let title = 'Posted';
+ if (channel) {
+ title = channel.display_name;
+ }
+
+ let notifyText = post.message.replace(/\n+/g, ' ');
+ if (notifyText.length > 50) {
+ notifyText = notifyText.substring(0, 49) + '...';
+ }
+
+ if (notifyText.length === 0) {
+ if (msgProps.image) {
+ Utils.notifyMe(title, username + ' uploaded an image', channel);
+ } else if (msgProps.otherFile) {
+ Utils.notifyMe(title, username + ' uploaded a file', channel);
+ } else {
+ Utils.notifyMe(title, username + ' did something new', channel);
+ }
+ } else {
+ Utils.notifyMe(title, username + ' wrote: ' + notifyText, channel);
+ }
+ if (!user.notify_props || user.notify_props.desktop_sound === 'true') {
+ Utils.ding();
+ }
+ }
+}
+
+function handlePostEditEvent(msg) {
+ // Store post
+ const post = JSON.parse(msg.props.post);
+ PostStore.storePost(post);
+
+ // Update channel state
+ if (ChannelStore.getCurrentId() === msg.channel_id) {
+ if (window.isActive) {
+ AsyncClient.updateLastViewedAt();
+ }
+ }
+}
+
+function handlePostDeleteEvent(msg) {
+ const post = JSON.parse(msg.props.post);
+
+ PostStore.storeUnseenDeletedPost(post);
+ PostStore.removePost(post, true);
+ PostStore.emitChange();
+}
+
+function handleNewUserEvent() {
+ AsyncClient.getProfiles();
+ AsyncClient.getChannelExtraInfo(true);
+}
+
+function handleUserAddedEvent(msg) {
+ if (ChannelStore.getCurrentId() === msg.channel_id) {
+ AsyncClient.getChannelExtraInfo(true);
+ }
+
+ if (UserStore.getCurrentId() === msg.user_id) {
+ AsyncClient.getChannel(msg.channel_id);
+ }
+}
+
+function handleUserRemovedEvent(msg) {
+ if (UserStore.getCurrentId() === msg.user_id) {
+ AsyncClient.getChannels();
+
+ if (msg.props.remover_id !== msg.user_id &&
+ msg.channel_id === ChannelStore.getCurrentId() &&
+ $('#removed_from_channel').length > 0) {
+ var sentState = {};
+ sentState.channelName = ChannelStore.getCurrent().display_name;
+ sentState.remover = UserStore.getProfile(msg.props.remover_id).username;
+
+ BrowserStore.setItem('channel-removed-state', sentState);
+ $('#removed_from_channel').modal('show');
+ }
+ } else if (ChannelStore.getCurrentId() === msg.channel_id) {
+ AsyncClient.getChannelExtraInfo(true);
+ }
+}
+
+function handleChannelViewedEvent(msg) {
+ // Useful for when multiple devices have the app open to different channels
+ if (ChannelStore.getCurrentId() !== msg.channel_id && UserStore.getCurrentId() === msg.user_id) {
+ AsyncClient.getChannel(msg.channel_id);
+ }
+}
+
var SocketStore = new SocketStoreClass();
SocketStore.dispatchToken = AppDispatcher.register((payload) => {
@@ -111,6 +283,7 @@ SocketStore.dispatchToken = AppDispatcher.register((payload) => {
switch (action.type) {
case ActionTypes.RECIEVED_MSG:
+ SocketStore.handleMessage(action.msg);
SocketStore.emitChange(action.msg);
break;
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index d6da91fe2..0911412d8 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -46,6 +46,18 @@ module.exports = {
SERVER_ACTION: null,
VIEW_ACTION: null
}),
+
+ SocketEvents: {
+ POSTED: 'posted',
+ POST_EDITED: 'post_edited',
+ POST_DELETED: 'post_deleted',
+ CHANNEL_VIEWED: 'channel_viewed',
+ NEW_USER: 'new_user',
+ USER_ADDED: 'user_added',
+ USER_REMOVED: 'user_removed',
+ TYPING: 'typing'
+ },
+
SPECIAL_MENTIONS: ['all', 'channel'],
CHARACTER_LIMIT: 4000,
IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png', 'jpeg'],
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 561c2c4c4..38ac68d58 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -13,7 +13,8 @@ var client = require('./client.jsx');
var Autolinker = require('autolinker');
export function isEmail(email) {
- var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
+ //var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
+ var regex = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i;
return regex.test(email);
}
diff --git a/web/sass-files/sass/partials/_command-box.scss b/web/sass-files/sass/partials/_command-box.scss
index f1aa4dca2..184fb55eb 100644
--- a/web/sass-files/sass/partials/_command-box.scss
+++ b/web/sass-files/sass/partials/_command-box.scss
@@ -5,6 +5,7 @@
border: $border-gray;
bottom: 38px;
overflow: auto;
+ z-index: 100;
@extend %popover-box-shadow;
.sidebar--right & {
bottom: 100px;
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index b942a5a40..0e474a1e2 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -140,7 +140,7 @@
padding: 0;
}
}
- .more-channel-table {
+ .more-table {
margin: 0;
table-layout: fixed;
p {
@@ -150,9 +150,11 @@
@include opacity(0.8);
margin: 5px 0;
}
- .more-channel-name {
+ .more-name {
font-weight: 600;
font-size: 0.95em;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
tbody {
> tr {
@@ -175,6 +177,9 @@
padding: 8px 15px 8px 8px;
width: 80px;
vertical-align: middle;
+ &.lg {
+ width: 110px;
+ }
}
}
}
@@ -331,47 +336,42 @@
}
.modal-direct-channels {
- .user-list {
- list-style-type: none;
- margin: 15px 0px 0px;
- max-height: 600px;
- padding: 0px;
- overflow: auto;
- li {
- border-bottom: 1px solid #ddd;
- height: 60px;
- padding: 10px 0px;
+ .user-list {
+ margin-top: 20px;
+ overflow: auto;
+ -webkit-overflow-scrolling: touch;
+ max-height: 500px;
+ position: relative;
+ }
- .image-div {
- padding: 0px;
+ .table {
+ margin-top: 10px;
+ }
- .profile-image {
- width: 40px;
- height: 40px;
- @include border-radius(20px);
- }
- }
+ .modal-body {
+ padding: 20px 0 0;
+ @include clearfix;
+ }
- .username {
- font-weight: bold;
- }
+ .filter-row {
+ padding: 0 15px;
+ }
- .nickname {
- color: #888;
- }
+ .member-count {
+ margin-top: 5px;
+ float: right;
+ @include opacity(0.8);
+ }
- .btn-div {
- padding: 0px;
- .btn-message {
- position: relative;
- top: 5px;
- }
- }
+ .more-description {
+ @include opacity(0.7);
+ }
- &:last-child {
- border-bottom: 0px;
- }
- }
- }
+ .profile-img {
+ -moz-border-radius: 50px;
+ -webkit-border-radius: 50px;
+ border-radius: 50px;
+ margin-right: 8px;
+ }
}
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index 09f2c179e..c41199cac 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -270,6 +270,13 @@
height: auto;
}
}
+ .modal-direct-channels {
+ .member-count {
+ float: none;
+ margin-top: 10px;
+ display: block;
+ }
+ }
.center-file-overlay {
font-size: 1.3em;
}
diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss
index 0c2f25eab..3146c16d5 100644
--- a/web/sass-files/sass/partials/_settings.scss
+++ b/web/sass-files/sass/partials/_settings.scss
@@ -230,13 +230,6 @@
font-weight:500;
}
-.profile-img {
- width:128px;
- height:128px;
- margin-bottom: 10px;
- @include border-radius(128px);
-}
-
.sel-btn {
margin-right:5px;
}
diff --git a/web/sass-files/sass/partials/_videos.scss b/web/sass-files/sass/partials/_videos.scss
index 9e1ce29b7..6ae5b488b 100644
--- a/web/sass-files/sass/partials/_videos.scss
+++ b/web/sass-files/sass/partials/_videos.scss
@@ -26,11 +26,6 @@
padding:0px;
}
-.video-uploader {
- font-size: 13px;
- margin: 0 0 15px;
-}
-
.video-title {
font-size:15px;
margin-top:3px;
@@ -54,4 +49,4 @@
border-top:36px solid transparent;
border-bottom:36px solid transparent;
border-left:60px solid rgba(255,255,255,0.4);
-} \ No newline at end of file
+}
diff --git a/web/templates/admin_console.html b/web/templates/admin_console.html
index a046478f6..574caf730 100644
--- a/web/templates/admin_console.html
+++ b/web/templates/admin_console.html
@@ -12,7 +12,7 @@
<div id='select_team_modal'></div>
<script>
- window.setup_admin_console_page();
+ window.setup_admin_console_page({{ .Props }});
$(document).ready(function(){
$('[data-toggle="tooltip"]').tooltip();
diff --git a/web/web.go b/web/web.go
index 836574855..7ea360a63 100644
--- a/web/web.go
+++ b/web/web.go
@@ -77,6 +77,9 @@ func InitWeb() {
mainrouter.Handle("/signup/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(signupCompleteOAuth)).Methods("GET")
mainrouter.Handle("/admin_console", api.UserRequired(adminConsole)).Methods("GET")
+ mainrouter.Handle("/admin_console/", api.UserRequired(adminConsole)).Methods("GET")
+ mainrouter.Handle("/admin_console/{tab:[A-Za-z0-9-_]+}", api.UserRequired(adminConsole)).Methods("GET")
+ mainrouter.Handle("/admin_console/{tab:[A-Za-z0-9-_]+}/{team:[A-Za-z0-9-]*}", api.UserRequired(adminConsole)).Methods("GET")
mainrouter.Handle("/hooks/{id:[A-Za-z0-9]+}", api.ApiAppHandler(incomingWebhook)).Methods("POST")
@@ -753,6 +756,7 @@ func adminConsole(c *api.Context, w http.ResponseWriter, r *http.Request) {
return
}
+<<<<<<< HEAD
teamChan := api.Srv.Store.Team().Get(c.Session.TeamId)
userChan := api.Srv.Store.User().Get(c.Session.UserId)
@@ -777,6 +781,16 @@ func adminConsole(c *api.Context, w http.ResponseWriter, r *http.Request) {
page.User = user
page.Team = team
page.Session = &c.Session
+=======
+ params := mux.Vars(r)
+ activeTab := params["tab"]
+ teamId := params["team"]
+
+ page := NewHtmlTemplatePage("admin_console", "Admin Console")
+
+ page.Props["ActiveTab"] = activeTab
+ page.Props["TeamId"] = teamId
+>>>>>>> master
page.Render(c, w)
}