Этот коммит содержится в:
Asaad Mahmood
2015-10-19 22:59:01 +05:00
родитель 2d42aefeaf fd69910fab
Коммит 5135986a3b
20 изменённых файлов: 328 добавлений и 201 удалений

Просмотреть файл

@@ -568,7 +568,7 @@ func updateLastViewedAt(c *Context, w http.ResponseWriter, r *http.Request) {
Srv.Store.Channel().UpdateLastViewedAt(id, c.Session.UserId) 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) message.Add("channel_id", id)
PublishAndForget(message) PublishAndForget(message)
@@ -777,9 +777,8 @@ func RemoveUserFromChannel(userIdToRemove string, removerUserId string, channel
UpdateChannelAccessCacheAndForget(channel.TeamId, userIdToRemove, channel.Id) UpdateChannelAccessCacheAndForget(channel.TeamId, userIdToRemove, channel.Id)
message := model.NewMessage(channel.TeamId, "", userIdToRemove, model.ACTION_USER_REMOVED) message := model.NewMessage(channel.TeamId, channel.Id, userIdToRemove, model.ACTION_USER_REMOVED)
message.Add("channel_id", channel.Id) message.Add("remover_id", removerUserId)
message.Add("remover", removerUserId)
PublishAndForget(message) PublishAndForget(message)
return nil return nil

Просмотреть файл

@@ -92,24 +92,9 @@ func (c *WebConn) writePump() {
return return
} }
if len(msg.ChannelId) > 0 { c.WebSocket.SetWriteDeadline(time.Now().Add(WRITE_WAIT))
allowed, ok := c.ChannelAccessCache[msg.ChannelId] if err := c.WebSocket.WriteJSON(msg); err != nil {
if !ok { return
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
}
} }
case <-ticker.C: 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)) allowed := hasPermissionsToChannel(Srv.Store.Channel().CheckPermissionsTo(c.TeamId, channelId, c.UserId))
c.ChannelAccessCache[channelId] = allowed c.ChannelAccessCache[channelId] = allowed
return allowed
} }
func hasPermissionsToChannel(sc store.StoreChannel) bool { func hasPermissionsToChannel(sc store.StoreChannel) bool {

Просмотреть файл

@@ -53,7 +53,7 @@ func (h *TeamHub) Start() {
} }
case msg := <-h.broadcast: case msg := <-h.broadcast:
for webCon := range h.connections { for webCon := range h.connections {
if !(webCon.UserId == msg.UserId && msg.Action == model.ACTION_TYPING) { if ShouldSendEvent(webCon, msg) {
select { select {
case webCon.Send <- msg: case webCon.Send <- msg:
default: 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
}

Просмотреть файл

@@ -17,6 +17,8 @@ Developer Machine Setup
2. Add the following to your ~/.bash_profile 2. Add the following to your ~/.bash_profile
`export GOPATH=$HOME/go` `export GOPATH=$HOME/go`
`export PATH=$PATH:$GOPATH/bin` `export PATH=$PATH:$GOPATH/bin`
`ulimit -n 8096`
If you don't increase the file handle limit you may see some weird build issues with browserify or npm.
3. Reload your bash profile 3. Reload your bash profile
`source ~/.bash_profile` `source ~/.bash_profile`
4. Install Node.js using Homebrew 4. Install Node.js using Homebrew
@@ -58,6 +60,8 @@ Any issues? Please let us know on our forums at: http://forum.mattermost.org
`export GOPATH=$HOME/go` `export GOPATH=$HOME/go`
`export GOROOT=/usr/local/go` `export GOROOT=/usr/local/go`
`export PATH=$PATH:$GOROOT/bin` `export PATH=$PATH:$GOROOT/bin`
`ulimit -n 8096`
If you don't increase the file handle limit you may see some weird build issues with browserify or npm.
3. Reload your bashrc 3. Reload your bashrc
`source ~/.bashrc` `source ~/.bashrc`
6. Install Node.js 6. Install Node.js

Просмотреть файл

@@ -160,10 +160,11 @@ exec bin/platform
proxy_set_header X-Forwarded-Ssl on; proxy_set_header X-Forwarded-Ssl on;
``` ```
## Finish Mattermost Server setup ## Finish Mattermost Server setup
1. Navigate to https://mattermost.example.com and create a team and user. 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. 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. 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 *Send Email Notifications* to true
* Set *Require Email Verification* to true * Set *Require Email Verification* to true
* Set *Feedback Name* to `No-Reply` * Set *Feedback Name* to `No-Reply`

Просмотреть файл

@@ -9,14 +9,14 @@ import (
) )
const ( const (
ACTION_TYPING = "typing" ACTION_TYPING = "typing"
ACTION_POSTED = "posted" ACTION_POSTED = "posted"
ACTION_POST_EDITED = "post_edited" ACTION_POST_EDITED = "post_edited"
ACTION_POST_DELETED = "post_deleted" ACTION_POST_DELETED = "post_deleted"
ACTION_VIEWED = "viewed" ACTION_CHANNEL_VIEWED = "channel_viewed"
ACTION_NEW_USER = "new_user" ACTION_NEW_USER = "new_user"
ACTION_USER_ADDED = "user_added" ACTION_USER_ADDED = "user_added"
ACTION_USER_REMOVED = "user_removed" ACTION_USER_REMOVED = "user_removed"
) )
type Message struct { type Message struct {

Просмотреть файл

@@ -6,15 +6,22 @@ package utils
import ( import (
l4g "code.google.com/p/log4go" l4g "code.google.com/p/log4go"
"crypto/tls" "crypto/tls"
"encoding/base64"
"fmt" "fmt"
"github.com/mattermost/platform/model" "github.com/mattermost/platform/model"
"html"
"net" "net"
"net/mail" "net/mail"
"net/smtp" "net/smtp"
"time" "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) { func connectToSMTPServer(config *model.Config) (net.Conn, *model.AppError) {
var conn net.Conn var conn net.Conn
var err error var err error
@@ -102,9 +109,10 @@ func SendMailUsingConfig(to, subject, body string, config *model.Config) *model.
headers := make(map[string]string) headers := make(map[string]string)
headers["From"] = fromMail.String() headers["From"] = fromMail.String()
headers["To"] = toMail.String() headers["To"] = toMail.String()
headers["Subject"] = html.UnescapeString(subject) headers["Subject"] = encodeRFC2047Word(subject)
headers["MIME-version"] = "1.0" 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) headers["Date"] = time.Now().Format(time.RFC1123Z)
message := "" message := ""

Просмотреть файл

@@ -40,9 +40,13 @@ export default class AdminController extends React.Component {
config: AdminStore.getConfig(), config: AdminStore.getConfig(),
teams: AdminStore.getAllTeams(), teams: AdminStore.getAllTeams(),
selectedTeams, selectedTeams,
selected: 'service_settings', selected: props.tab || 'service_settings',
selectedTeam: null selectedTeam: props.teamId || null
}; };
if (!props.tab) {
history.replaceState(null, null, `/admin_console/${this.state.selected}`);
}
} }
componentDidMount() { componentDidMount() {
@@ -142,7 +146,9 @@ export default class AdminController extends React.Component {
} else if (this.state.selected === 'service_settings') { } else if (this.state.selected === 'service_settings') {
tab = <ServiceSettingsTab config={this.state.config} />; tab = <ServiceSettingsTab config={this.state.config} />;
} else if (this.state.selected === 'team_users') { } 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]} />;
}
} }
} }

Просмотреть файл

@@ -24,6 +24,7 @@ export default class AdminSidebar extends React.Component {
handleClick(name, teamId, e) { handleClick(name, teamId, e) {
e.preventDefault(); e.preventDefault();
this.props.selectTab(name, teamId); this.props.selectTab(name, teamId);
history.pushState({name: name, teamId: teamId}, null, `/admin_console/${name}/${teamId || ''}`);
} }
isSelected(name, teamId) { isSelected(name, teamId) {

Просмотреть файл

@@ -4,7 +4,6 @@
const ChannelStore = require('../stores/channel_store.jsx'); const ChannelStore = require('../stores/channel_store.jsx');
const UserStore = require('../stores/user_store.jsx'); const UserStore = require('../stores/user_store.jsx');
const PostStore = require('../stores/post_store.jsx'); const PostStore = require('../stores/post_store.jsx');
const SocketStore = require('../stores/socket_store.jsx');
const NavbarSearchBox = require('./search_bar.jsx'); const NavbarSearchBox = require('./search_bar.jsx');
const AsyncClient = require('../utils/async_client.jsx'); const AsyncClient = require('../utils/async_client.jsx');
const Client = require('../utils/client.jsx'); const Client = require('../utils/client.jsx');
@@ -25,7 +24,6 @@ export default class ChannelHeader extends React.Component {
super(props); super(props);
this.onListenerChange = this.onListenerChange.bind(this); this.onListenerChange = this.onListenerChange.bind(this);
this.onSocketChange = this.onSocketChange.bind(this);
this.handleLeave = this.handleLeave.bind(this); this.handleLeave = this.handleLeave.bind(this);
this.searchMentions = this.searchMentions.bind(this); this.searchMentions = this.searchMentions.bind(this);
@@ -45,7 +43,6 @@ export default class ChannelHeader extends React.Component {
ChannelStore.addExtraInfoChangeListener(this.onListenerChange); ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
PostStore.addSearchChangeListener(this.onListenerChange); PostStore.addSearchChangeListener(this.onListenerChange);
UserStore.addChangeListener(this.onListenerChange); UserStore.addChangeListener(this.onListenerChange);
SocketStore.addChangeListener(this.onSocketChange);
} }
componentWillUnmount() { componentWillUnmount() {
ChannelStore.removeChangeListener(this.onListenerChange); 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}}); $('.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() { handleLeave() {
Client.leaveChannel(this.state.channel.id, Client.leaveChannel(this.state.channel.id,
function handleLeaveSuccess() { () => {
AppDispatcher.handleViewAction({ AppDispatcher.handleViewAction({
type: ActionTypes.LEAVE_CHANNEL, type: ActionTypes.LEAVE_CHANNEL,
id: this.state.channel.id id: this.state.channel.id
@@ -77,8 +67,8 @@ export default class ChannelHeader extends React.Component {
const townsquare = ChannelStore.getByName('town-square'); const townsquare = ChannelStore.getByName('town-square');
Utils.switchChannel(townsquare); Utils.switchChannel(townsquare);
}.bind(this), },
function handleLeaveError(err) { (err) => {
AsyncClient.dispatchError(err, 'handleLeave'); AsyncClient.dispatchError(err, 'handleLeave');
} }
); );

Просмотреть файл

@@ -1,8 +1,11 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
var SocketStore = require('../stores/socket_store.jsx'); const SocketStore = require('../stores/socket_store.jsx');
var UserStore = require('../stores/user_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 { export default class MsgTyping extends React.Component {
constructor(props) { constructor(props) {
@@ -33,9 +36,9 @@ export default class MsgTyping extends React.Component {
} }
onChange(msg) { onChange(msg) {
if (msg.action === 'typing' && if (msg.action === SocketEvents.TYPING &&
this.props.channelId === msg.channel_id && this.props.channelId === msg.channel_id &&
this.props.parentId === msg.props.parent_id) { this.props.parentId === msg.props.parent_id) {
this.lastTime = new Date().getTime(); this.lastTime = new Date().getTime();
var username = 'Someone'; var username = 'Someone';
@@ -52,7 +55,7 @@ export default class MsgTyping extends React.Component {
} }
}.bind(this), 3000); }.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: ''}); this.setState({text: ''});
} }
} }

Просмотреть файл

@@ -1,20 +1,24 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
var PostStore = require('../stores/post_store.jsx'); const Post = require('./post.jsx');
var ChannelStore = require('../stores/channel_store.jsx'); const UserProfile = require('./user_profile.jsx');
var UserStore = require('../stores/user_store.jsx'); const AsyncClient = require('../utils/async_client.jsx');
var PreferenceStore = require('../stores/preference_store.jsx'); const LoadingScreen = require('./loading_screen.jsx');
var UserProfile = require('./user_profile.jsx');
var AsyncClient = require('../utils/async_client.jsx'); const PostStore = require('../stores/post_store.jsx');
var Post = require('./post.jsx'); const ChannelStore = require('../stores/channel_store.jsx');
var LoadingScreen = require('./loading_screen.jsx'); const UserStore = require('../stores/user_store.jsx');
var SocketStore = require('../stores/socket_store.jsx'); const SocketStore = require('../stores/socket_store.jsx');
var utils = require('../utils/utils.jsx'); const PreferenceStore = require('../stores/preference_store.jsx');
var Client = require('../utils/client.jsx');
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); const utils = require('../utils/utils.jsx');
var Constants = require('../utils/constants.jsx'); const Client = require('../utils/client.jsx');
var ActionTypes = Constants.ActionTypes; 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 { export default class PostList extends React.Component {
constructor(props) { 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) { if (postList.posts[a].create_at > postList.posts[b].create_at) {
return -1; return -1;
} }
@@ -82,7 +86,7 @@ export default class PostList extends React.Component {
} }
return { return {
postList: postList postList
}; };
} }
componentDidMount() { componentDidMount() {
@@ -263,14 +267,14 @@ export default class PostList extends React.Component {
Client.getPosts( Client.getPosts(
id, id,
PostStore.getLatestUpdate(id), PostStore.getLatestUpdate(id),
function success() { () => {
this.loadInProgress = false; this.loadInProgress = false;
this.setState({isFirstLoadComplete: true}); this.setState({isFirstLoadComplete: true});
}.bind(this), },
function fail() { () => {
this.loadInProgress = false; this.loadInProgress = false;
this.setState({isFirstLoadComplete: true}); this.setState({isFirstLoadComplete: true});
}.bind(this) }
); );
} }
onChange() { onChange() {
@@ -281,28 +285,16 @@ export default class PostList extends React.Component {
} }
} }
onSocketChange(msg) { onSocketChange(msg) {
var post; if (msg.action === SocketEvents.POST_DELETED) {
if (msg.action === 'posted' || msg.action === 'post_edited') {
post = JSON.parse(msg.props.post);
PostStore.storePost(post);
} else if (msg.action === 'post_deleted') {
var activeRoot = $(document.activeElement).closest('.comment-create-body')[0]; var activeRoot = $(document.activeElement).closest('.comment-create-body')[0];
var activeRootPostId = ''; var activeRootPostId = '';
if (activeRoot && activeRoot.id.length > 0) { if (activeRoot && activeRoot.id.length > 0) {
activeRootPostId = activeRoot.id; 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) { if (activeRootPostId === msg.props.post_id && UserStore.getCurrentId() !== msg.user_id) {
$('#post_deleted').modal('show'); $('#post_deleted').modal('show');
} }
} else if (msg.action === 'new_user') {
AsyncClient.getProfiles();
} }
} }
onTimeChange() { onTimeChange() {
@@ -352,7 +344,7 @@ export default class PostList extends React.Component {
data-title={channel.display_name} data-title={channel.display_name}
data-channelid={channel.id} data-channelid={channel.id}
> >
<i className='fa fa-pencil'></i>Set a description <i className='fa fa-pencil'></i>{'Set a description'}
</a> </a>
</div> </div>
); );

Просмотреть файл

@@ -2,7 +2,6 @@
// See License.txt for license information. // See License.txt for license information.
const AsyncClient = require('../utils/async_client.jsx'); const AsyncClient = require('../utils/async_client.jsx');
const BrowserStore = require('../stores/browser_store.jsx');
const ChannelStore = require('../stores/channel_store.jsx'); const ChannelStore = require('../stores/channel_store.jsx');
const Client = require('../utils/client.jsx'); const Client = require('../utils/client.jsx');
const Constants = require('../utils/constants.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 MoreDirectChannels = require('./more_direct_channels.jsx');
const SearchBox = require('./search_bar.jsx'); const SearchBox = require('./search_bar.jsx');
const SidebarHeader = require('./sidebar_header.jsx'); const SidebarHeader = require('./sidebar_header.jsx');
const SocketStore = require('../stores/socket_store.jsx');
const TeamStore = require('../stores/team_store.jsx'); const TeamStore = require('../stores/team_store.jsx');
const UnreadChannelIndicator = require('./unread_channel_indicator.jsx'); const UnreadChannelIndicator = require('./unread_channel_indicator.jsx');
const UserStore = require('../stores/user_store.jsx'); const UserStore = require('../stores/user_store.jsx');
@@ -129,7 +127,6 @@ export default class Sidebar extends React.Component {
UserStore.addChangeListener(this.onChange); UserStore.addChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onChange); UserStore.addStatusesChangeListener(this.onChange);
TeamStore.addChangeListener(this.onChange); TeamStore.addChangeListener(this.onChange);
SocketStore.addChangeListener(this.onSocketChange);
PreferenceStore.addChangeListener(this.onChange); PreferenceStore.addChangeListener(this.onChange);
if ($(window).width() > 768) { if ($(window).width() > 768) {
@@ -162,7 +159,6 @@ export default class Sidebar extends React.Component {
UserStore.removeChangeListener(this.onChange); UserStore.removeChangeListener(this.onChange);
UserStore.removeStatusesChangeListener(this.onChange); UserStore.removeStatusesChangeListener(this.onChange);
TeamStore.removeChangeListener(this.onChange); TeamStore.removeChangeListener(this.onChange);
SocketStore.removeChangeListener(this.onSocketChange);
PreferenceStore.removeChangeListener(this.onChange); PreferenceStore.removeChangeListener(this.onChange);
} }
onChange() { onChange() {
@@ -171,94 +167,6 @@ export default class Sidebar extends React.Component {
this.setState(newState); 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() { updateTitle() {
const channel = ChannelStore.getCurrent(); const channel = ChannelStore.getCurrent();
if (channel) { if (channel) {

Просмотреть файл

@@ -86,11 +86,11 @@ export default class UserProfile extends React.Component {
dataContent.push( dataContent.push(
<div <div
data-toggle='tooltip' data-toggle='tooltip'
title="' + this.state.profile.email + '" title={this.state.profile.email}
key='user-popover-email' key='user-popover-email'
> >
<a <a
href="mailto:' + this.state.profile.email + '" href={'mailto:' + this.state.profile.email}
className='text-nowrap text-lowercase user-popover__email' className='text-nowrap text-lowercase user-popover__email'
> >
{this.state.profile.email} {this.state.profile.email}

Просмотреть файл

@@ -6,6 +6,7 @@ const Utils = require('../utils/utils.jsx');
const Constants = require('../utils/constants.jsx'); const Constants = require('../utils/constants.jsx');
const ViewImagePopoverBar = require('./view_image_popover_bar.jsx'); const ViewImagePopoverBar = require('./view_image_popover_bar.jsx');
const Modal = ReactBootstrap.Modal; const Modal = ReactBootstrap.Modal;
const KeyCodes = Constants.KeyCodes;
export default class ViewImageModal extends React.Component { export default class ViewImageModal extends React.Component {
constructor(props) { constructor(props) {
@@ -63,11 +64,11 @@ export default class ViewImageModal extends React.Component {
this.loadImage(id); this.loadImage(id);
} }
handleKeyPress(e) { handleKeyPress(e) {
if (!e) { if (!e || !this.props.show) {
return; return;
} else if (e.keyCode === 39) { } else if (e.keyCode === KeyCodes.RIGHT) {
this.handleNext(); this.handleNext();
} else if (e.keyCode === 37) { } else if (e.keyCode === KeyCodes.LEFT) {
this.handlePrev(); this.handlePrev();
} }
} }

Просмотреть файл

@@ -5,9 +5,12 @@ var ErrorBar = require('../components/error_bar.jsx');
var SelectTeamModal = require('../components/admin_console/select_team_modal.jsx'); var SelectTeamModal = require('../components/admin_console/select_team_modal.jsx');
var AdminController = require('../components/admin_console/admin_controller.jsx'); var AdminController = require('../components/admin_console/admin_controller.jsx');
export function setupAdminConsolePage() { export function setupAdminConsolePage(props) {
ReactDOM.render( ReactDOM.render(
<AdminController />, <AdminController
tab={props.ActiveTab}
teamId={props.TeamId}
/>,
document.getElementById('admin_controller') document.getElementById('admin_controller')
); );

Просмотреть файл

@@ -1,15 +1,22 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var UserStore = require('./user_store.jsx'); const UserStore = require('./user_store.jsx');
var ErrorStore = require('./error_store.jsx'); const PostStore = require('./post_store.jsx');
var EventEmitter = require('events').EventEmitter; 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'); const Utils = require('../utils/utils.jsx');
var ActionTypes = Constants.ActionTypes; 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; var conn;
@@ -94,6 +101,39 @@ class SocketStoreClass extends EventEmitter {
removeChangeListener(callback) { removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, 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) { sendMessage(msg) {
if (conn && conn.readyState === WebSocket.OPEN) { if (conn && conn.readyState === WebSocket.OPEN) {
conn.send(JSON.stringify(msg)); 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(); var SocketStore = new SocketStoreClass();
SocketStore.dispatchToken = AppDispatcher.register((payload) => { SocketStore.dispatchToken = AppDispatcher.register((payload) => {
@@ -111,6 +283,7 @@ SocketStore.dispatchToken = AppDispatcher.register((payload) => {
switch (action.type) { switch (action.type) {
case ActionTypes.RECIEVED_MSG: case ActionTypes.RECIEVED_MSG:
SocketStore.handleMessage(action.msg);
SocketStore.emitChange(action.msg); SocketStore.emitChange(action.msg);
break; break;

Просмотреть файл

@@ -47,6 +47,18 @@ module.exports = {
SERVER_ACTION: null, SERVER_ACTION: null,
VIEW_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'], SPECIAL_MENTIONS: ['all', 'channel'],
CHARACTER_LIMIT: 4000, CHARACTER_LIMIT: 4000,
IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png', 'jpeg'], IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png', 'jpeg'],

Просмотреть файл

@@ -12,7 +12,7 @@
<div id='select_team_modal'></div> <div id='select_team_modal'></div>
<script> <script>
window.setup_admin_console_page(); window.setup_admin_console_page({{ .Props }});
$(document).ready(function(){ $(document).ready(function(){
$('[data-toggle="tooltip"]').tooltip(); $('[data-toggle="tooltip"]').tooltip();

Просмотреть файл

@@ -64,6 +64,9 @@ func InitWeb() {
mainrouter.Handle("/signup/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(signupCompleteOAuth)).Methods("GET") 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/", 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") mainrouter.Handle("/hooks/{id:[A-Za-z0-9]+}", api.ApiAppHandler(incomingWebhook)).Methods("POST")
@@ -699,7 +702,14 @@ func adminConsole(c *api.Context, w http.ResponseWriter, r *http.Request) {
return return
} }
params := mux.Vars(r)
activeTab := params["tab"]
teamId := params["team"]
page := NewHtmlTemplatePage("admin_console", "Admin Console") page := NewHtmlTemplatePage("admin_console", "Admin Console")
page.Props["ActiveTab"] = activeTab
page.Props["TeamId"] = teamId
page.Render(c, w) page.Render(c, w)
} }