* PLT-4167 Team Sidebar

* Address feedback from PM

* change route from my_members to members

* bug fixes

* Updating styles for teams sidebar (#4681)

* Added PM changes

* Fix corner cases

* Addressing feedback

* use two different endpoints

* Bug fixes

* Rename model and client functions, using preferences to store last team and channel viewed

* Fix mobile notification count and closing the team sidebar

* unit test, fixed bad merge and retrieve from cached when available

* bug fixes

* use id for last channel in preferences, query optimization

* Updating multi team css (#4830)
Этот коммит содержится в:
enahum
2016-12-19 10:05:46 -03:00
коммит произвёл Joram Wilander
родитель 3ce2ce9dc8
Коммит 999d1553e1
50 изменённых файлов: 1139 добавлений и 183 удалений

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

@@ -846,14 +846,21 @@ func setLastViewedAt(c *Context, w http.ResponseWriter, r *http.Request) {
Srv.Store.Channel().SetLastViewedAt(id, c.Session.UserId, newLastViewedAt) Srv.Store.Channel().SetLastViewedAt(id, c.Session.UserId, newLastViewedAt)
preference := model.Preference{ chanPref := model.Preference{
UserId: c.Session.UserId, UserId: c.Session.UserId,
Category: model.PREFERENCE_CATEGORY_LAST, Category: c.TeamId,
Name: model.PREFERENCE_NAME_LAST_CHANNEL, Name: model.PREFERENCE_NAME_LAST_CHANNEL,
Value: id, Value: id,
} }
Srv.Store.Preference().Save(&model.Preferences{preference}) teamPref := model.Preference{
UserId: c.Session.UserId,
Category: model.PREFERENCE_CATEGORY_LAST,
Name: model.PREFERENCE_NAME_LAST_TEAM,
Value: c.TeamId,
}
Srv.Store.Preference().Save(&model.Preferences{teamPref, chanPref})
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_VIEWED, c.TeamId, "", c.Session.UserId, nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_VIEWED, c.TeamId, "", c.Session.UserId, nil)
message.Add("channel_id", id) message.Add("channel_id", id)
@@ -901,14 +908,21 @@ func updateLastViewedAt(c *Context, w http.ResponseWriter, r *http.Request) {
go clearPushNotification(c.Session.UserId, id) go clearPushNotification(c.Session.UserId, id)
} }
preference := model.Preference{ chanPref := model.Preference{
UserId: c.Session.UserId, UserId: c.Session.UserId,
Category: model.PREFERENCE_CATEGORY_LAST, Category: c.TeamId,
Name: model.PREFERENCE_NAME_LAST_CHANNEL, Name: model.PREFERENCE_NAME_LAST_CHANNEL,
Value: id, Value: id,
} }
Srv.Store.Preference().Save(&model.Preferences{preference}) teamPref := model.Preference{
UserId: c.Session.UserId,
Category: model.PREFERENCE_CATEGORY_LAST,
Name: model.PREFERENCE_NAME_LAST_TEAM,
Value: c.TeamId,
}
Srv.Store.Preference().Save(&model.Preferences{teamPref, chanPref})
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_VIEWED, c.TeamId, "", c.Session.UserId, nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_VIEWED, c.TeamId, "", c.Session.UserId, nil)
message.Add("channel_id", id) message.Add("channel_id", id)

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

@@ -824,6 +824,7 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
message.Add("post", post.ToJson()) message.Add("post", post.ToJson())
message.Add("channel_type", channel.Type) message.Add("channel_type", channel.Type)
message.Add("channel_display_name", channel.DisplayName) message.Add("channel_display_name", channel.DisplayName)
message.Add("channel_name", channel.Name)
message.Add("sender_name", senderUsername) message.Add("sender_name", senderUsername)
message.Add("team_id", team.Id) message.Add("team_id", team.Id)

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

@@ -32,6 +32,8 @@ func InitTeam() {
BaseRoutes.Teams.Handle("/get_invite_info", ApiAppHandler(getInviteInfo)).Methods("POST") BaseRoutes.Teams.Handle("/get_invite_info", ApiAppHandler(getInviteInfo)).Methods("POST")
BaseRoutes.Teams.Handle("/find_team_by_name", ApiAppHandler(findTeamByName)).Methods("POST") BaseRoutes.Teams.Handle("/find_team_by_name", ApiAppHandler(findTeamByName)).Methods("POST")
BaseRoutes.Teams.Handle("/name/{team_name:[A-Za-z0-9\\-]+}", ApiAppHandler(getTeamByName)).Methods("GET") BaseRoutes.Teams.Handle("/name/{team_name:[A-Za-z0-9\\-]+}", ApiAppHandler(getTeamByName)).Methods("GET")
BaseRoutes.Teams.Handle("/members", ApiUserRequired(getMyTeamMembers)).Methods("GET")
BaseRoutes.Teams.Handle("/unread", ApiUserRequired(getMyTeamsUnread)).Methods("GET")
BaseRoutes.NeedTeam.Handle("/me", ApiUserRequired(getMyTeam)).Methods("GET") BaseRoutes.NeedTeam.Handle("/me", ApiUserRequired(getMyTeam)).Methods("GET")
BaseRoutes.NeedTeam.Handle("/stats", ApiUserRequired(getTeamStats)).Methods("GET") BaseRoutes.NeedTeam.Handle("/stats", ApiUserRequired(getTeamStats)).Methods("GET")
@@ -358,6 +360,11 @@ func LeaveTeam(team *model.Team, user *model.User) *model.AppError {
return uua.Err return uua.Err
} }
// delete the preferences that set the last channel used in the team and other team specific preferences
if result := <-Srv.Store.Preference().DeleteCategory(user.Id, team.Id); result.Err != nil {
return result.Err
}
RemoveAllSessionsForUserId(user.Id) RemoveAllSessionsForUserId(user.Id)
InvalidateCacheForUser(user.Id) InvalidateCacheForUser(user.Id)
@@ -723,6 +730,32 @@ func getTeamByName(c *Context, w http.ResponseWriter, r *http.Request) {
} }
} }
func getMyTeamMembers(c *Context, w http.ResponseWriter, r *http.Request) {
if len(c.Session.TeamMembers) > 0 {
w.Write([]byte(model.TeamMembersToJson(c.Session.TeamMembers)))
} else {
if result := <-Srv.Store.Team().GetTeamsForUser(c.Session.UserId); result.Err != nil {
c.Err = result.Err
return
} else {
data := result.Data.([]*model.TeamMember)
w.Write([]byte(model.TeamMembersToJson(data)))
}
}
}
func getMyTeamsUnread(c *Context, w http.ResponseWriter, r *http.Request) {
teamId := r.URL.Query().Get("id")
if result := <-Srv.Store.Team().GetTeamsUnreadForUser(teamId, c.Session.UserId); result.Err != nil {
c.Err = result.Err
return
} else {
data := result.Data.([]*model.TeamUnread)
w.Write([]byte(model.TeamsUnreadToJson(data)))
}
}
func InviteMembers(team *model.Team, senderName string, invites []string) { func InviteMembers(team *model.Team, senderName string, invites []string) {
for _, invite := range invites { for _, invite := range invites {
if len(invite) > 0 { if len(invite) > 0 {
@@ -801,6 +834,10 @@ func updateTeam(c *Context, w http.ResponseWriter, r *http.Request) {
oldTeam.Sanitize() oldTeam.Sanitize()
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_UPDATE_TEAM, "", "", "", nil)
message.Add("team", oldTeam.ToJson())
go Publish(message)
w.Write([]byte(oldTeam.ToJson())) w.Write([]byte(oldTeam.ToJson()))
} }

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

@@ -576,6 +576,41 @@ func TestGetTeamMembers(t *testing.T) {
} }
} }
func TestGetMyTeamMembers(t *testing.T) {
th := Setup().InitBasic()
if result, err := th.BasicClient.GetMyTeamMembers(); err != nil {
t.Fatal(err)
} else {
members := result.Data.([]*model.TeamMember)
if len(members) == 0 {
t.Fatal("should have results")
}
}
}
func TestGetMyTeamsUnread(t *testing.T) {
th := Setup().InitBasic()
if result, err := th.BasicClient.GetMyTeamsUnread(""); err != nil {
t.Fatal(err)
} else {
members := result.Data.([]*model.TeamUnread)
if len(members) == 0 {
t.Fatal("should have results")
}
}
if result, err := th.BasicClient.GetMyTeamsUnread(th.BasicTeam.Id); err != nil {
t.Fatal(err)
} else {
members := result.Data.([]*model.TeamUnread)
if len(members) != 0 {
t.Fatal("should not have results")
}
}
}
func TestGetTeamMember(t *testing.T) { func TestGetTeamMember(t *testing.T) {
th := Setup().InitBasic() th := Setup().InitBasic()

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

@@ -4863,6 +4863,10 @@
"id": "store.sql_team.get_teams_for_email.app_error", "id": "store.sql_team.get_teams_for_email.app_error",
"translation": "We encountered a problem when looking up teams" "translation": "We encountered a problem when looking up teams"
}, },
{
"id": "store.sql_team.get_unread.app_error",
"translation": "We couldn't get the teams unread messages"
},
{ {
"id": "store.sql_team.permanent_delete.app_error", "id": "store.sql_team.permanent_delete.app_error",
"translation": "We couldn't delete the existing team" "translation": "We couldn't delete the existing team"

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

@@ -1752,6 +1752,36 @@ func (c *Client) GetTeamMembers(teamId string, offset int, limit int) (*Result,
} }
} }
// GetMyTeamMembers will return an array with team member objects that the current user
// is a member of. Must be authenticated.
func (c *Client) GetMyTeamMembers() (*Result, *AppError) {
if r, err := c.DoApiGet("/teams/members", "", ""); err != nil {
return nil, err
} else {
defer closeBody(r)
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), TeamMembersFromJson(r.Body)}, nil
}
}
// GetMyTeamsUnread will return an array with TeamUnread objects that contain the amount of
// unread messages and mentions the current user has for the teams it belongs to.
// An optional team ID can be set to exclude that team from the results. Must be authenticated.
func (c *Client) GetMyTeamsUnread(teamId string) (*Result, *AppError) {
endpoint := "/teams/unread"
if teamId != "" {
endpoint += fmt.Sprintf("?id=%s", url.QueryEscape(teamId))
}
if r, err := c.DoApiGet(endpoint, "", ""); err != nil {
return nil, err
} else {
defer closeBody(r)
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), TeamsUnreadFromJson(r.Body)}, nil
}
}
// GetTeamMember will return a team member object based on the team id and user id provided. // GetTeamMember will return a team member object based on the team id and user id provided.
// Must be authenticated. // Must be authenticated.
func (c *Client) GetTeamMember(teamId string, userId string) (*Result, *AppError) { func (c *Client) GetTeamMember(teamId string, userId string) (*Result, *AppError) {

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

@@ -33,6 +33,7 @@ const (
PREFERENCE_CATEGORY_LAST = "last" PREFERENCE_CATEGORY_LAST = "last"
PREFERENCE_NAME_LAST_CHANNEL = "channel" PREFERENCE_NAME_LAST_CHANNEL = "channel"
PREFERENCE_NAME_LAST_TEAM = "team"
PREFERENCE_CATEGORY_NOTIFICATIONS = "notifications" PREFERENCE_CATEGORY_NOTIFICATIONS = "notifications"
PREFERENCE_NAME_EMAIL_INTERVAL = "email_interval" PREFERENCE_NAME_EMAIL_INTERVAL = "email_interval"

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

@@ -16,6 +16,12 @@ type TeamMember struct {
DeleteAt int64 `json:"delete_at"` DeleteAt int64 `json:"delete_at"`
} }
type TeamUnread struct {
TeamId string `json:"team_id"`
MsgCount int64 `json:"msg_count"`
MentionCount int64 `json:"mention_count"`
}
func (o *TeamMember) ToJson() string { func (o *TeamMember) ToJson() string {
b, err := json.Marshal(o) b, err := json.Marshal(o)
if err != nil { if err != nil {
@@ -55,6 +61,25 @@ func TeamMembersFromJson(data io.Reader) []*TeamMember {
} }
} }
func TeamsUnreadToJson(o []*TeamUnread) string {
if b, err := json.Marshal(o); err != nil {
return "[]"
} else {
return string(b)
}
}
func TeamsUnreadFromJson(data io.Reader) []*TeamUnread {
decoder := json.NewDecoder(data)
var o []*TeamUnread
err := decoder.Decode(&o)
if err == nil {
return o
} else {
return nil
}
}
func (o *TeamMember) IsValid() *AppError { func (o *TeamMember) IsValid() *AppError {
if len(o.TeamId) != 26 { if len(o.TeamId) != 26 {

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

@@ -18,6 +18,7 @@ const (
WEBSOCKET_EVENT_DIRECT_ADDED = "direct_added" WEBSOCKET_EVENT_DIRECT_ADDED = "direct_added"
WEBSOCKET_EVENT_NEW_USER = "new_user" WEBSOCKET_EVENT_NEW_USER = "new_user"
WEBSOCKET_EVENT_LEAVE_TEAM = "leave_team" WEBSOCKET_EVENT_LEAVE_TEAM = "leave_team"
WEBSOCKET_EVENT_UPDATE_TEAM = "update_team"
WEBSOCKET_EVENT_USER_ADDED = "user_added" WEBSOCKET_EVENT_USER_ADDED = "user_added"
WEBSOCKET_EVENT_USER_UPDATED = "user_updated" WEBSOCKET_EVENT_USER_UPDATED = "user_updated"
WEBSOCKET_EVENT_USER_REMOVED = "user_removed" WEBSOCKET_EVENT_USER_REMOVED = "user_removed"

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

@@ -326,3 +326,25 @@ func (s SqlPreferenceStore) Delete(userId, category, name string) StoreChannel {
return storeChannel return storeChannel
} }
func (s SqlPreferenceStore) DeleteCategory(userId string, category string) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
if _, err := s.GetMaster().Exec(
`DELETE FROM
Preferences
WHERE
UserId = :UserId
AND Category = :Category`, map[string]interface{}{"UserId": userId, "Category": category}); err != nil {
result.Err = model.NewLocAppError("SqlPreferenceStore.DeleteCategory", "store.sql_preference.delete.app_error", nil, err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}

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

@@ -392,3 +392,38 @@ func TestPreferenceDelete(t *testing.T) {
t.Fatal("should've returned no preferences") t.Fatal("should've returned no preferences")
} }
} }
func TestPreferenceDeleteCategory(t *testing.T) {
Setup()
category := model.NewId()
userId := model.NewId()
preference1 := model.Preference{
UserId: userId,
Category: category,
Name: model.NewId(),
Value: "value1a",
}
preference2 := model.Preference{
UserId: userId,
Category: category,
Name: model.NewId(),
Value: "value1a",
}
Must(store.Preference().Save(&model.Preferences{preference1, preference2}))
if prefs := Must(store.Preference().GetAll(userId)).(model.Preferences); len([]model.Preference(prefs)) != 2 {
t.Fatal("should've returned 2 preferences")
}
if result := <-store.Preference().DeleteCategory(userId, category); result.Err != nil {
t.Fatal(result.Err)
}
if prefs := Must(store.Preference().GetAll(userId)).(model.Preferences); len([]model.Preference(prefs)) != 0 {
t.Fatal("should've returned no preferences")
}
}

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

@@ -583,6 +583,39 @@ func (s SqlTeamStore) GetTeamsForUser(userId string) StoreChannel {
return storeChannel return storeChannel
} }
func (s SqlTeamStore) GetTeamsUnreadForUser(teamId, userId string) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
result := StoreResult{}
var members []*model.TeamUnread
_, err := s.GetReplica().Select(&members,
`SELECT
Channels.TeamId,
SUM(Channels.TotalMsgCount - ChannelMembers.MsgCount) as MsgCount,
SUM(ChannelMembers.MentionCount) as MentionCount
FROM
Channels,
ChannelMembers
WHERE
Channels.Id = ChannelMembers.ChannelId AND
ChannelMembers.UserId = :UserId AND Channels.TeamId != :TeamId
GROUP BY
Channels.TeamId`, map[string]interface{}{"UserId": userId, "TeamId": teamId})
if err != nil {
result.Err = model.NewLocAppError("SqlTeamStore.GetTeamsUnreadForUser", "store.sql_team.get_unread.app_error", nil, "userId="+userId+" "+err.Error())
} else {
result.Data = members
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlTeamStore) RemoveMember(teamId string, userId string) StoreChannel { func (s SqlTeamStore) RemoveMember(teamId string, userId string) StoreChannel {
storeChannel := make(StoreChannel, 1) storeChannel := make(StoreChannel, 1)

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

@@ -529,3 +529,50 @@ func TestTeamStoreMemberCount(t *testing.T) {
} }
} }
} }
func TestMyTeamMembersUnread(t *testing.T) {
Setup()
teamId1 := model.NewId()
teamId2 := model.NewId()
uid := model.NewId()
m1 := &model.TeamMember{TeamId: teamId1, UserId: uid}
m2 := &model.TeamMember{TeamId: teamId2, UserId: uid}
Must(store.Team().SaveMember(m1))
Must(store.Team().SaveMember(m2))
c1 := &model.Channel{TeamId: m1.TeamId, Name: model.NewId(), DisplayName: "Town Square", Type: model.CHANNEL_OPEN}
Must(store.Channel().Save(c1))
c2 := &model.Channel{TeamId: m2.TeamId, Name: model.NewId(), DisplayName: "Town Square", Type: model.CHANNEL_OPEN}
Must(store.Channel().Save(c2))
cm1 := &model.ChannelMember{ChannelId: c1.Id, UserId: m1.UserId, NotifyProps: model.GetDefaultChannelNotifyProps()}
Must(store.Channel().SaveMember(cm1))
cm2 := &model.ChannelMember{ChannelId: c2.Id, UserId: m2.UserId, NotifyProps: model.GetDefaultChannelNotifyProps()}
Must(store.Channel().SaveMember(cm2))
if r1 := <-store.Team().GetTeamsUnreadForUser("", uid); r1.Err != nil {
t.Fatal(r1.Err)
} else {
ms := r1.Data.([]*model.TeamUnread)
if len(ms) != 2 {
t.Fatal("Should be the unreads for all the teams")
}
}
if r2 := <-store.Team().GetTeamsUnreadForUser(teamId1, uid); r2.Err != nil {
t.Fatal(r2.Err)
} else {
ms := r2.Data.([]*model.TeamUnread)
if len(ms) != 1 {
t.Fatal("Should be the unreads for just one team")
}
}
if r1 := <-store.Team().RemoveAllMembersByUser(uid); r1.Err != nil {
t.Fatal(r1.Err)
}
}

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

@@ -74,6 +74,7 @@ type TeamStore interface {
GetTotalMemberCount(teamId string) StoreChannel GetTotalMemberCount(teamId string) StoreChannel
GetActiveMemberCount(teamId string) StoreChannel GetActiveMemberCount(teamId string) StoreChannel
GetTeamsForUser(userId string) StoreChannel GetTeamsForUser(userId string) StoreChannel
GetTeamsUnreadForUser(teamId, userId string) StoreChannel
RemoveMember(teamId string, userId string) StoreChannel RemoveMember(teamId string, userId string) StoreChannel
RemoveAllMembersByTeam(teamId string) StoreChannel RemoveAllMembersByTeam(teamId string) StoreChannel
RemoveAllMembersByUser(userId string) StoreChannel RemoveAllMembersByUser(userId string) StoreChannel
@@ -270,6 +271,7 @@ type PreferenceStore interface {
GetCategory(userId string, category string) StoreChannel GetCategory(userId string, category string) StoreChannel
GetAll(userId string) StoreChannel GetAll(userId string) StoreChannel
Delete(userId, category, name string) StoreChannel Delete(userId, category, name string) StoreChannel
DeleteCategory(userId string, category string) StoreChannel
PermanentDeleteByUser(userId string) StoreChannel PermanentDeleteByUser(userId string) StoreChannel
IsFeatureEnabled(feature, userId string) StoreChannel IsFeatureEnabled(feature, userId string) StoreChannel
} }

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

@@ -43,6 +43,7 @@ export function emitChannelClickEvent(channel) {
); );
} }
function switchToChannel(chan) { function switchToChannel(chan) {
const channelMember = ChannelStore.getMyMember(chan.id);
const getMyChannelMembersPromise = AsyncClient.getChannelMember(chan.id, UserStore.getCurrentId()); const getMyChannelMembersPromise = AsyncClient.getChannelMember(chan.id, UserStore.getCurrentId());
getMyChannelMembersPromise.then(() => { getMyChannelMembersPromise.then(() => {
@@ -56,6 +57,9 @@ export function emitChannelClickEvent(channel) {
type: ActionTypes.CLICK_CHANNEL, type: ActionTypes.CLICK_CHANNEL,
name: chan.name, name: chan.name,
id: chan.id, id: chan.id,
team_id: chan.team_id,
total_msg_count: chan.total_msg_count,
channelMember,
prev: ChannelStore.getCurrentId() prev: ChannelStore.getCurrentId()
}); });
} }
@@ -443,7 +447,7 @@ export function viewLoggedIn() {
PostStore.clearPendingPosts(); PostStore.clearPendingPosts();
} }
var lastTimeTypingSent = 0; let lastTimeTypingSent = 0;
export function emitLocalUserTypingEvent(channelId, parentId) { export function emitLocalUserTypingEvent(channelId, parentId) {
const t = Date.now(); const t = Date.now();
if ((t - lastTimeTypingSent) > Constants.UPDATE_TYPING_MS) { if ((t - lastTimeTypingSent) > Constants.UPDATE_TYPING_MS) {
@@ -534,3 +538,35 @@ export function emitBrowserFocus(focus) {
focus focus
}); });
} }
export function redirectUserToDefaultTeam() {
const teams = TeamStore.getAll();
const teamMembers = TeamStore.getMyTeamMembers();
let teamId = PreferenceStore.get('last', 'team');
if (!teams[teamId] && teamMembers.length > 0) {
let myTeams = [];
for (const index in teamMembers) {
if (teamMembers.hasOwnProperty(index)) {
const teamMember = teamMembers[index];
myTeams.push(teams[teamMember.team_id]);
}
}
if (myTeams.length > 0) {
myTeams = myTeams.sort((a, b) => a.display_name.localeCompare(b.display_name));
teamId = myTeams[0].id;
}
}
if (teams[teamId]) {
const channelId = PreferenceStore.get(teamId, 'channel');
let channel = ChannelStore.getChannelById(channelId);
if (!channel) {
channel = 'town-square';
}
browserHistory.push(`/${teams[teamId].name}/channels/${channel}`);
} else {
browserHistory.push('/select_team');
}
}

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

@@ -18,23 +18,30 @@ const ActionTypes = Constants.ActionTypes;
const Preferences = Constants.Preferences; const Preferences = Constants.Preferences;
export function handleNewPost(post, msg) { export function handleNewPost(post, msg) {
const teamId = TeamStore.getCurrentId();
if (ChannelStore.getCurrentId() === post.channel_id) { if (ChannelStore.getCurrentId() === post.channel_id) {
if (window.isActive) { if (window.isActive) {
AsyncClient.updateLastViewedAt(null, false); AsyncClient.updateLastViewedAt(null, false);
} else { } else {
AsyncClient.getChannel(post.channel_id); AsyncClient.getChannel(post.channel_id);
} }
} else if (msg && (TeamStore.getCurrentId() === msg.data.team_id || msg.data.channel_type === Constants.DM_CHANNEL)) { } else if (msg && (teamId === msg.data.team_id || msg.data.channel_type === Constants.DM_CHANNEL)) {
if (Client.teamId) { if (Client.teamId) {
AsyncClient.getChannel(post.channel_id); AsyncClient.getChannel(post.channel_id);
} }
} }
var websocketMessageProps = null; let websocketMessageProps = null;
if (msg) { if (msg) {
websocketMessageProps = msg.data; websocketMessageProps = msg.data;
} }
const myTeams = TeamStore.getMyTeamMembers();
if (msg.data.team_id !== teamId && myTeams.filter((m) => m.team_id === msg.data.team_id).length) {
AsyncClient.getMyTeamsUnread(teamId);
}
if (post.root_id && PostStore.getPost(post.channel_id, post.root_id) == null) { if (post.root_id && PostStore.getPost(post.channel_id, post.root_id) == null) {
Client.getPost( Client.getPost(
post.channel_id, post.channel_id,

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

@@ -6,6 +6,7 @@ import $ from 'jquery';
import UserStore from 'stores/user_store.jsx'; import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx'; import TeamStore from 'stores/team_store.jsx';
import PostStore from 'stores/post_store.jsx'; import PostStore from 'stores/post_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import ChannelStore from 'stores/channel_store.jsx'; import ChannelStore from 'stores/channel_store.jsx';
import BrowserStore from 'stores/browser_store.jsx'; import BrowserStore from 'stores/browser_store.jsx';
import ErrorStore from 'stores/error_store.jsx'; import ErrorStore from 'stores/error_store.jsx';
@@ -122,6 +123,10 @@ function handleEvent(msg) {
handleLeaveTeamEvent(msg); handleLeaveTeamEvent(msg);
break; break;
case SocketEvents.UPDATE_TEAM:
handleUpdateTeamEvent(msg);
break;
case SocketEvents.USER_ADDED: case SocketEvents.USER_ADDED:
handleUserAddedEvent(msg); handleUserAddedEvent(msg);
break; break;
@@ -229,21 +234,37 @@ function handleNewUserEvent(msg) {
AsyncClient.getUser(msg.data.user_id); AsyncClient.getUser(msg.data.user_id);
AsyncClient.getChannelStats(); AsyncClient.getChannelStats();
loadProfilesAndTeamMembersForDMSidebar(); loadProfilesAndTeamMembersForDMSidebar();
if (msg.data.user_id === UserStore.getCurrentId()) {
AsyncClient.getMyTeamMembers();
}
} }
function handleLeaveTeamEvent(msg) { function handleLeaveTeamEvent(msg) {
if (UserStore.getCurrentId() === msg.data.user_id) { if (UserStore.getCurrentId() === msg.data.user_id) {
TeamStore.removeMyTeamMember(msg.data.team_id); TeamStore.removeMyTeamMember(msg.data.team_id);
// if they are on the team being removed redirect them to the root // if they are on the team being removed redirect them to default team
if (TeamStore.getCurrentId() === msg.data.team_id) { if (TeamStore.getCurrentId() === msg.data.team_id) {
TeamStore.setCurrentId(''); TeamStore.setCurrentId('');
Client.setTeamId(''); Client.setTeamId('');
browserHistory.push('/'); PreferenceStore.deletePreference({
category: 'last',
name: 'team',
value: msg.data.team_id
});
GlobalActions.redirectUserToDefaultTeam();
} }
} else {
UserStore.removeProfileFromTeam(msg.data.team_id, msg.data.user_id);
TeamStore.removeMemberInTeam(msg.data.team_id, msg.data.user_id);
} }
} }
function handleUpdateTeamEvent(msg) {
TeamStore.updateTeam(msg.data.team);
}
function handleDirectAddedEvent(msg) { function handleDirectAddedEvent(msg) {
AsyncClient.getChannel(msg.broadcast.channel_id); AsyncClient.getChannel(msg.broadcast.channel_id);
loadProfilesAndTeamMembersForDMSidebar(); loadProfilesAndTeamMembersForDMSidebar();

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

@@ -598,6 +598,30 @@ export default class Client {
end(this.handleResponse.bind(this, 'getTeamMember', success, error)); end(this.handleResponse.bind(this, 'getTeamMember', success, error));
} }
getMyTeamMembers(success, error) {
request.
get(`${this.getTeamsRoute()}/members`).
set(this.defaultHeaders).
type('application/json').
accept('application/json').
end(this.handleResponse.bind(this, 'getMyTeamMembers', success, error));
}
getMyTeamsUnread(teamId, success, error) {
let url = `${this.getTeamsRoute()}/unread`;
if (teamId) {
url += `?id=${encodeURIComponent(teamId)}`;
}
request.
get(url).
set(this.defaultHeaders).
type('application/json').
accept('application/json').
end(this.handleResponse.bind(this, 'getMyTeamsUnread', success, error));
}
getTeamMembersByIds(teamId, userIds, success, error) { getTeamMembersByIds(teamId, userIds, success, error) {
request. request.
post(`${this.getTeamNeededRoute(teamId)}/members/ids`). post(`${this.getTeamNeededRoute(teamId)}/members/ids`).

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

@@ -50,13 +50,14 @@ export default class AdminNavbarDropdown extends React.Component {
} }
render() { render() {
var teams = []; const teams = [];
let switchTeams;
if (this.state.teamMembers && this.state.teamMembers.length > 0) { if (this.state.teamMembers && this.state.teamMembers.length > 0) {
for (var index in this.state.teamMembers) { for (const index in this.state.teamMembers) {
if (this.state.teamMembers.hasOwnProperty(index)) { if (this.state.teamMembers.hasOwnProperty(index)) {
var teamMember = this.state.teamMembers[index]; const teamMember = this.state.teamMembers[index];
var team = this.state.teams[teamMember.team_id]; const team = this.state.teams[teamMember.team_id];
teams.push( teams.push(
<li key={'team_' + team.name}> <li key={'team_' + team.name}>
<Link <Link
@@ -79,6 +80,20 @@ export default class AdminNavbarDropdown extends React.Component {
className='divider' className='divider'
/> />
); );
} else {
switchTeams = (
<li>
<Link
to={'/select_team'}
>
<i className='fa fa-exchange'/>
<FormattedMessage
id='admin.nav.switch'
defaultMessage='Team Selection'
/>
</Link>
</li>
);
} }
return ( return (
@@ -104,17 +119,7 @@ export default class AdminNavbarDropdown extends React.Component {
role='menu' role='menu'
> >
{teams} {teams}
<li> {switchTeams}
<Link
to={'/select_team'}
>
<i className='fa fa-exchange'/>
<FormattedMessage
id='admin.nav.switch'
defaultMessage='Team Selection'
/>
</Link>
</li>
<li <li
key='teamDiv' key='teamDiv'
className='divider' className='divider'

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

@@ -2,6 +2,8 @@
// See License.txt for license information. // See License.txt for license information.
import ErrorBar from 'components/error_bar.jsx'; import ErrorBar from 'components/error_bar.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
import {browserHistory, Link} from 'react-router/es6'; import {browserHistory, Link} from 'react-router/es6';
@@ -47,11 +49,21 @@ export default class CreateTeamController extends React.Component {
); );
} }
let url = '/select_team';
const team = TeamStore.getCurrent();
const channel = ChannelStore.getCurrent();
if (team) {
url = `/${team.name}`;
if (channel) {
url += `/channels/${channel.name}`;
}
}
return ( return (
<div> <div>
<ErrorBar/> <ErrorBar/>
<div className='signup-header'> <div className='signup-header'>
<Link to='/select_team'> <Link to={url}>
<span className='fa fa-chevron-left'/> <span className='fa fa-chevron-left'/>
<FormattedMessage <FormattedMessage
id='web.header.back' id='web.header.back'

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

@@ -54,7 +54,7 @@ export default class LoginController extends React.Component {
document.title = global.window.mm_config.SiteName; document.title = global.window.mm_config.SiteName;
if (UserStore.getCurrentUser()) { if (UserStore.getCurrentUser()) {
browserHistory.push('/select_team'); GlobalActions.redirectUserToDefaultTeam();
} }
AsyncClient.checkVersion(); AsyncClient.checkVersion();
@@ -205,7 +205,7 @@ export default class LoginController extends React.Component {
} else if (team) { } else if (team) {
browserHistory.push(`/${team.name}`); browserHistory.push(`/${team.name}`);
} else { } else {
browserHistory.push('/select_team'); GlobalActions.redirectUserToDefaultTeam();
} }
} }
); );

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

@@ -146,6 +146,7 @@ export default class Navbar extends React.Component {
if (e.target.className !== 'navbar-toggle' && e.target.className !== 'icon-bar') { if (e.target.className !== 'navbar-toggle' && e.target.className !== 'icon-bar') {
$('.app__body .inner-wrap').removeClass('move--right move--left move--left-small'); $('.app__body .inner-wrap').removeClass('move--right move--left move--left-small');
$('.app__body .sidebar--left').removeClass('move--right'); $('.app__body .sidebar--left').removeClass('move--right');
$('.multi-teams .team-sidebar').removeClass('move--right');
$('.app__body .sidebar--right').removeClass('move--left'); $('.app__body .sidebar--right').removeClass('move--left');
$('.app__body .sidebar--menu').removeClass('move--left'); $('.app__body .sidebar--menu').removeClass('move--left');
} }
@@ -155,6 +156,7 @@ export default class Navbar extends React.Component {
toggleLeftSidebar() { toggleLeftSidebar() {
$('.app__body .inner-wrap').toggleClass('move--right'); $('.app__body .inner-wrap').toggleClass('move--right');
$('.app__body .sidebar--left').toggleClass('move--right'); $('.app__body .sidebar--left').toggleClass('move--right');
$('.multi-teams .team-sidebar').toggleClass('move--right');
} }
toggleRightSidebar() { toggleRightSidebar() {

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

@@ -141,6 +141,7 @@ export default class NeedsTeam extends React.Component {
content.push( content.push(
this.props.navbar this.props.navbar
); );
content.push(this.props.team_sidebar);
content.push( content.push(
this.props.sidebar this.props.sidebar
); );
@@ -197,6 +198,7 @@ NeedsTeam.propTypes = {
]), ]),
navbar: React.PropTypes.element, navbar: React.PropTypes.element,
sidebar: React.PropTypes.element, sidebar: React.PropTypes.element,
team_sidebar: React.PropTypes.element,
center: React.PropTypes.element, center: React.PropTypes.element,
params: React.PropTypes.object, params: React.PropTypes.object,
user: React.PropTypes.object user: React.PropTypes.object

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

@@ -3,14 +3,22 @@
import * as utils from 'utils/utils.jsx'; import * as utils from 'utils/utils.jsx';
import ChannelStore from 'stores/channel_store.jsx'; import ChannelStore from 'stores/channel_store.jsx';
import TeamStore from 'stores/team_store.jsx';
function getCountsStateFromStores() { function getCountsStateFromStores() {
var count = 0; let count = 0;
var channels = ChannelStore.getAll(); const teamMembers = TeamStore.getMyTeamMembers();
var members = ChannelStore.getMyMembers(); const channels = ChannelStore.getAll();
const members = ChannelStore.getMyMembers();
teamMembers.forEach((member) => {
if (member.team_id !== TeamStore.getCurrentId()) {
count += ((member.msg_count || 0) + (member.mention_count || 0));
}
});
channels.forEach((channel) => { channels.forEach((channel) => {
var channelMember = members[channel.id]; const channelMember = members[channel.id];
if (channelMember == null) { if (channelMember == null) {
return; return;
} }
@@ -41,10 +49,12 @@ export default class NotifyCounts extends React.Component {
componentDidMount() { componentDidMount() {
this.mounted = true; this.mounted = true;
ChannelStore.addChangeListener(this.onListenerChange); ChannelStore.addChangeListener(this.onListenerChange);
TeamStore.addChangeListener(this.onListenerChange);
} }
componentWillUnmount() { componentWillUnmount() {
this.mounted = false; this.mounted = false;
ChannelStore.removeChangeListener(this.onListenerChange); ChannelStore.removeChangeListener(this.onListenerChange);
TeamStore.removeChangeListener(this.onListenerChange);
} }
onListenerChange() { onListenerChange() {
if (this.mounted) { if (this.mounted) {

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

@@ -50,7 +50,7 @@ export default class Root extends React.Component {
if (UserStore.getNoAccounts()) { if (UserStore.getNoAccounts()) {
browserHistory.push('/signup_user_complete'); browserHistory.push('/signup_user_complete');
} else if (UserStore.getCurrentUser()) { } else if (UserStore.getCurrentUser()) {
browserHistory.push('/select_team'); GlobalActions.redirectUserToDefaultTeam();
} else { } else {
browserHistory.push('/login'); browserHistory.push('/login');
} }

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

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

@@ -24,6 +24,7 @@ export default class SelectTeam extends React.Component {
super(props); super(props);
this.onTeamChange = this.onTeamChange.bind(this); this.onTeamChange = this.onTeamChange.bind(this);
this.handleTeamClick = this.handleTeamClick.bind(this); this.handleTeamClick = this.handleTeamClick.bind(this);
this.teamContentsCompare = this.teamContentsCompare.bind(this);
const state = this.getStateFromStores(false); const state = this.getStateFromStores(false);
state.loadingTeamId = ''; state.loadingTeamId = '';
@@ -61,33 +62,15 @@ export default class SelectTeam extends React.Component {
} }
render() { render() {
let content = null; let openTeamContents = [];
let teamContents = [];
const isAlreadyMember = new Map(); const isAlreadyMember = new Map();
const isSystemAdmin = Utils.isSystemAdmin(UserStore.getCurrentUser().roles); const isSystemAdmin = Utils.isSystemAdmin(UserStore.getCurrentUser().roles);
let teamMembersCount = 0;
for (const teamMember of this.state.teamMembers) { for (const teamMember of this.state.teamMembers) {
const teamId = teamMember.team_id; const teamId = teamMember.team_id;
const team = this.state.teams[teamId];
isAlreadyMember[teamId] = true; isAlreadyMember[teamId] = true;
teamMembersCount++;
teamContents.push(
<SelectTeamItem
key={'team_' + team.name}
team={team}
url={'/' + team.name + '/channels/town-square'}
onTeamClick={this.handleTeamClick}
loading={this.state.loadingTeamId === teamId}
/>
);
} }
teamContents = teamContents.sort(this.teamContentsCompare);
var openTeamContents = [];
for (const id in this.state.teamListings) { for (const id in this.state.teamListings) {
if (this.state.teamListings.hasOwnProperty(id) && !isAlreadyMember[id]) { if (this.state.teamListings.hasOwnProperty(id) && !isAlreadyMember[id]) {
const openTeam = this.state.teamListings[id]; const openTeam = this.state.teamListings[id];
@@ -103,8 +86,8 @@ export default class SelectTeam extends React.Component {
} }
} }
if (teamMembersCount === 0 && teamContents.length === 0 && openTeamContents.length === 0 && (global.window.mm_config.EnableTeamCreation === 'true' || isSystemAdmin)) { if (openTeamContents.length === 0 && (global.window.mm_config.EnableTeamCreation === 'true' || isSystemAdmin)) {
teamContents = ( openTeamContents = (
<div className='signup-team-dir-err'> <div className='signup-team-dir-err'>
<div> <div>
<FormattedMessage <FormattedMessage
@@ -114,8 +97,8 @@ export default class SelectTeam extends React.Component {
</div> </div>
</div> </div>
); );
} else if (teamMembersCount === 0 && teamContents.length === 0 && openTeamContents.length === 0) { } else if (openTeamContents.length === 0) {
teamContents = ( openTeamContents = (
<div className='signup-team-dir-err'> <div className='signup-team-dir-err'>
<div> <div>
<FormattedMessage <FormattedMessage
@@ -125,42 +108,25 @@ export default class SelectTeam extends React.Component {
</div> </div>
</div> </div>
); );
} else if (teamContents.length === 0 && openTeamContents.length > 0) {
teamContents = null;
} }
if (teamContents) { if (Array.isArray(openTeamContents)) {
content = ( openTeamContents = openTeamContents.sort(this.teamContentsCompare);
<div className='signup__content'>
<h4>
<FormattedMessage
id='signup_team.choose'
defaultMessage='Your teams:'
/>
</h4>
<div className='signup-team-all'>
{teamContents}
</div>
</div>
);
} }
var openContent; let openContent = (
if (openTeamContents.length > 0) { <div className='signup__content'>
openContent = ( <h4>
<div className='signup__content'> <FormattedMessage
<h4> id='signup_team.join_open'
<FormattedMessage defaultMessage='Teams you can join: '
id='signup_team.join_open' />
defaultMessage='Teams you can join: ' </h4>
/> <div className='signup-team-all'>
</h4> {openTeamContents}
<div className='signup-team-all'>
{openTeamContents.sort(this.teamContentsCompare)}
</div>
</div> </div>
); </div>
} );
if (!this.state.loaded) { if (!this.state.loaded) {
openContent = <LoadingScreen/>; openContent = <LoadingScreen/>;
@@ -177,7 +143,7 @@ export default class SelectTeam extends React.Component {
} }
let teamSignUp; let teamSignUp;
if (isSystemAdmin || (global.window.mm_config.EnableTeamCreation === 'true' && !UserAgent.isMobileApp())) { if (isSystemAdmin || global.window.mm_config.EnableTeamCreation === 'true') {
teamSignUp = ( teamSignUp = (
<div className='margin--extra'> <div className='margin--extra'>
<Link <Link
@@ -197,7 +163,7 @@ export default class SelectTeam extends React.Component {
} }
let adminConsoleLink; let adminConsoleLink;
if (isSystemAdmin) { if (isSystemAdmin && !UserAgent.isMobileApp()) {
adminConsoleLink = ( adminConsoleLink = (
<div className='margin--extra hidden-xs'> <div className='margin--extra hidden-xs'>
<Link <Link
@@ -225,19 +191,30 @@ export default class SelectTeam extends React.Component {
); );
} }
let headerButton;
if (this.state.teamMembers.length) {
headerButton = (
<Link to='/'>
<span className='fa fa-chevron-left'/>
<FormattedMessage id='web.header.back'/>
</Link>
);
} else {
headerButton = (
<a
href='#'
onClick={GlobalActions.emitUserLoggedOutEvent}
>
<span className='fa fa-chevron-left'/>
<FormattedMessage id='web.header.logout'/>
</a>
);
}
return ( return (
<div> <div>
<ErrorBar/> <ErrorBar/>
<div className='signup-header'> <div className='signup-header'>
<a {headerButton}
href='#'
onClick={GlobalActions.emitUserLoggedOutEvent}
>
<span className='fa fa-chevron-left'/>
<FormattedMessage
id='navbar_dropdown.logout'
/>
</a>
</div> </div>
<div className='col-sm-12'> <div className='col-sm-12'>
<div className={'signup-team__container'}> <div className={'signup-team__container'}>
@@ -249,7 +226,6 @@ export default class SelectTeam extends React.Component {
<h4 className='color--light'> <h4 className='color--light'>
{description} {description}
</h4> </h4>
{content}
{openContent} {openContent}
{teamSignUp} {teamSignUp}
{adminConsoleLink} {adminConsoleLink}

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

@@ -82,11 +82,19 @@ export default class Sidebar extends React.Component {
let msgs = 0; let msgs = 0;
let mentions = 0; let mentions = 0;
const unreadCounts = this.state.unreadCounts; const unreadCounts = this.state.unreadCounts;
const teamMembers = this.state.teamMembers;
teamMembers.forEach((member) => {
if (member.team_id !== this.state.currentTeam.id) {
msgs += member.msg_count || 0;
mentions += member.mention_count || 0;
}
});
Object.keys(unreadCounts).forEach((chId) => { Object.keys(unreadCounts).forEach((chId) => {
const channel = ChannelStore.get(chId); const channel = ChannelStore.get(chId);
if (channel && (!channel.team_id || channel.team_id === this.state.currentTeam.id)) { if (channel && (channel.type === 'D' || channel.team_id === this.state.currentTeam.id)) {
msgs += unreadCounts[chId].msgs; msgs += unreadCounts[chId].msgs;
mentions += unreadCounts[chId].mentions; mentions += unreadCounts[chId].mentions;
} }
@@ -97,6 +105,7 @@ export default class Sidebar extends React.Component {
getStateFromStores() { getStateFromStores() {
const members = ChannelStore.getMyMembers(); const members = ChannelStore.getMyMembers();
const teamMembers = TeamStore.getMyTeamMembers();
const currentChannelId = ChannelStore.getCurrentId(); const currentChannelId = ChannelStore.getCurrentId();
const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999); const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
const channelList = ChannelUtils.buildDisplayableChannelList(Object.assign([], ChannelStore.getAll())); const channelList = ChannelUtils.buildDisplayableChannelList(Object.assign([], ChannelStore.getAll()));
@@ -104,6 +113,7 @@ export default class Sidebar extends React.Component {
return { return {
activeId: currentChannelId, activeId: currentChannelId,
members, members,
teamMembers,
...channelList, ...channelList,
unreadCounts: JSON.parse(JSON.stringify(ChannelStore.getUnreadCounts())), unreadCounts: JSON.parse(JSON.stringify(ChannelStore.getUnreadCounts())),
showTutorialTip: tutorialStep === TutorialSteps.CHANNEL_POPOVER, showTutorialTip: tutorialStep === TutorialSteps.CHANNEL_POPOVER,
@@ -153,6 +163,7 @@ export default class Sidebar extends React.Component {
if (this.state.activeId !== prevState.activeId) { if (this.state.activeId !== prevState.activeId) {
$('.app__body .inner-wrap').removeClass('move--right'); $('.app__body .inner-wrap').removeClass('move--right');
$('.app__body .sidebar--left').removeClass('move--right'); $('.app__body .sidebar--left').removeClass('move--right');
$('.multi-teams .team-sidebar').removeClass('move--right');
} }
} }
@@ -169,6 +180,7 @@ export default class Sidebar extends React.Component {
onChange() { onChange() {
this.setState(this.getStateFromStores()); this.setState(this.getStateFromStores());
this.updateTitle();
} }
updateTitle() { updateTitle() {

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

@@ -45,7 +45,6 @@ export default class SidebarHeaderDropdown extends React.Component {
this.showGetTeamInviteLinkModal = this.showGetTeamInviteLinkModal.bind(this); this.showGetTeamInviteLinkModal = this.showGetTeamInviteLinkModal.bind(this);
this.showTeamMembersModal = this.showTeamMembersModal.bind(this); this.showTeamMembersModal = this.showTeamMembersModal.bind(this);
this.hideTeamMembersModal = this.hideTeamMembersModal.bind(this); this.hideTeamMembersModal = this.hideTeamMembersModal.bind(this);
this.handleSwitchTeams = this.handleSwitchTeams.bind(this);
this.onTeamChange = this.onTeamChange.bind(this); this.onTeamChange = this.onTeamChange.bind(this);
this.openAccountSettings = this.openAccountSettings.bind(this); this.openAccountSettings = this.openAccountSettings.bind(this);
@@ -55,8 +54,8 @@ export default class SidebarHeaderDropdown extends React.Component {
this.handleClick = this.handleClick.bind(this); this.handleClick = this.handleClick.bind(this);
this.state = { this.state = {
teams: TeamStore.getAll(),
teamMembers: TeamStore.getMyTeamMembers(), teamMembers: TeamStore.getMyTeamMembers(),
teamListings: TeamStore.getTeamListings(),
showAboutModal: false, showAboutModal: false,
showDropdown: false, showDropdown: false,
showTeamMembersModal: false, showTeamMembersModal: false,
@@ -131,11 +130,6 @@ export default class SidebarHeaderDropdown extends React.Component {
}); });
} }
handleSwitchTeams() {
// The actual switching of teams is handled by the react-router Link
this.setState({showDropdown: false});
}
componentDidMount() { componentDidMount() {
TeamStore.addChangeListener(this.onTeamChange); TeamStore.addChangeListener(this.onTeamChange);
document.addEventListener('keydown', this.openAccountSettings); document.addEventListener('keydown', this.openAccountSettings);
@@ -143,8 +137,8 @@ export default class SidebarHeaderDropdown extends React.Component {
onTeamChange() { onTeamChange() {
this.setState({ this.setState({
teams: TeamStore.getAll(), teamMembers: TeamStore.getMyTeamMembers(),
teamMembers: TeamStore.getMyTeamMembers() teamListings: TeamStore.getTeamListings()
}); });
} }
@@ -182,14 +176,14 @@ export default class SidebarHeaderDropdown extends React.Component {
render() { render() {
const config = global.mm_config; const config = global.mm_config;
var teamLink = ''; const currentUser = this.props.currentUser;
var inviteLink = ''; let teamLink = '';
var manageLink = ''; let inviteLink = '';
var sysAdminLink = ''; let manageLink = '';
var currentUser = this.props.currentUser; let sysAdminLink = '';
var isAdmin = false; let isAdmin = false;
var isSystemAdmin = false; let isSystemAdmin = false;
var teamSettings = null; let teamSettings = null;
let integrationsLink = null; let integrationsLink = null;
if (!currentUser) { if (!currentUser) {
@@ -322,6 +316,7 @@ export default class SidebarHeaderDropdown extends React.Component {
} }
const teams = []; const teams = [];
let moreTeams = false;
if (config.EnableTeamCreation === 'true') { if (config.EnableTeamCreation === 'true') {
teams.push( teams.push(
@@ -340,6 +335,31 @@ export default class SidebarHeaderDropdown extends React.Component {
); );
} }
const isAlreadyMember = this.state.teamMembers.reduce((result, item) => {
result[item.team_id] = true;
return result;
}, {});
for (const id in this.state.teamListings) {
if (this.state.teamListings.hasOwnProperty(id) && !isAlreadyMember[id]) {
moreTeams = true;
break;
}
}
if (moreTeams) {
teams.push(
<li key='joinTeam_li'>
<Link to='/select_team'>
<FormattedMessage
id='navbar_dropdown.join'
defaultMessage='Join Another Team'
/>
</Link>
</li>
);
}
teams.push( teams.push(
<li key='leaveTeam_li'> <li key='leaveTeam_li'>
<a <a
@@ -354,39 +374,6 @@ export default class SidebarHeaderDropdown extends React.Component {
</li> </li>
); );
if (this.state.teamMembers && this.state.teamMembers.length > 1) {
teams.push(
<li
key='teamDiv'
className='divider'
/>
);
for (var index in this.state.teamMembers) {
if (this.state.teamMembers.hasOwnProperty(index)) {
var teamMember = this.state.teamMembers[index];
var team = this.state.teams[teamMember.team_id];
if (team.name !== this.props.teamName) {
teams.push(
<li key={'team_' + team.name}>
<Link
to={'/' + team.name + '/channels/town-square'}
onClick={this.handleSwitchTeams}
>
<FormattedMessage
id='navbar_dropdown.switchTo'
defaultMessage='Switch to '
/>
{team.display_name}
</Link>
</li>
);
}
}
}
}
let helpLink = null; let helpLink = null;
if (config.HelpLink) { if (config.HelpLink) {
helpLink = ( helpLink = (

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

@@ -82,6 +82,7 @@ export default class SidebarRight extends React.Component {
$('.app__body .inner-wrap').removeClass('.move--right'); $('.app__body .inner-wrap').removeClass('.move--right');
$('.app__body .inner-wrap').addClass('move--left'); $('.app__body .inner-wrap').addClass('move--left');
$('.app__body .sidebar--left').removeClass('move--right'); $('.app__body .sidebar--left').removeClass('move--right');
$('.multi-teams .team-sidebar').removeClass('move--right');
$('.app__body .sidebar--right').addClass('move--left'); $('.app__body .sidebar--right').addClass('move--left');
//$('.sidebar--right').prepend('<div class="sidebar__overlay"></div>'); //$('.sidebar--right').prepend('<div class="sidebar__overlay"></div>');

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

@@ -391,15 +391,6 @@ export default class SidebarRightMenu extends React.Component {
{teamSettingsLink} {teamSettingsLink}
{manageLink} {manageLink}
{consoleLink} {consoleLink}
<li>
<Link to='/select_team'>
<i className='icon fa fa-exchange'/>
<FormattedMessage
id='sidebar_right_menu.switch_team'
defaultMessage='Team Selection'
/>
</Link>
</li>
<li className='divider'/> <li className='divider'/>
{helpLink} {helpLink}
{reportLink} {reportLink}

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

@@ -110,7 +110,7 @@ export default class SignupEmail extends React.Component {
if (query.redirect_to) { if (query.redirect_to) {
browserHistory.push(query.redirect_to); browserHistory.push(query.redirect_to);
} else { } else {
browserHistory.push('/select_team'); GlobalActions.redirectUserToDefaultTeam();
} }
} }
); );
@@ -133,7 +133,7 @@ export default class SignupEmail extends React.Component {
if (query.redirect_to) { if (query.redirect_to) {
browserHistory.push(query.redirect_to); browserHistory.push(query.redirect_to);
} else { } else {
browserHistory.push('/select_team'); GlobalActions.redirectUserToDefaultTeam();
} }
} }
); );

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

@@ -90,8 +90,13 @@ export default class SignupLdap extends React.Component {
finishSignup() { finishSignup() {
GlobalActions.emitInitialLoad( GlobalActions.emitInitialLoad(
() => { () => {
const query = this.props.location.query;
GlobalActions.loadDefaultLocale(); GlobalActions.loadDefaultLocale();
browserHistory.push('/select_team'); if (query.redirect_to) {
browserHistory.push(query.redirect_to);
} else {
GlobalActions.redirectUserToDefaultTeam();
}
} }
); );
} }

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

@@ -122,7 +122,7 @@ export default class SignupController extends React.Component {
} }
if (userLoggedIn) { if (userLoggedIn) {
browserHistory.push('/select_team'); GlobalActions.redirectUserToDefaultTeam();
} }
} }
} }

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

@@ -0,0 +1,84 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import Constants from 'utils/constants.jsx';
import React from 'react';
import {Link} from 'react-router/es6';
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
export default class TeamButton extends React.Component {
constructor(props) {
super(props);
this.handleDisabled = this.handleDisabled.bind(this);
}
handleDisabled(e) {
e.preventDefault();
}
render() {
let teamClass = this.props.active ? 'active' : '';
const disabled = this.props.disabled ? 'team-disabled' : '';
const handleClick = (this.props.active || this.props.disabled) ? this.handleDisabled : null;
let badge;
if (!teamClass) {
teamClass = this.props.unread ? 'unread' : '';
if (this.props.mentions) {
badge = (
<span className='badge pull-right small'>{this.props.mentions}</span>
);
}
}
return (
<div
className={`team-container ${teamClass}`}
>
<Link
className={disabled}
to={this.props.url}
onClick={handleClick}
>
<OverlayTrigger
delayShow={Constants.OVERLAY_TIME_DELAY}
placement={this.props.placement}
overlay={
<Tooltip id={`tooltip-${this.props.url}`}>
{this.props.tip}
</Tooltip>
}
>
<div className='team-btn'>
{badge}
{this.props.contents}
</div>
</OverlayTrigger>
</Link>
</div>
);
}
}
TeamButton.defaultProps = {
tip: '',
placement: 'right',
active: false,
disabled: false,
unread: false,
mentions: 0
};
TeamButton.propTypes = {
url: React.PropTypes.string.isRequired,
contents: React.PropTypes.node.isRequired,
tip: React.PropTypes.node,
active: React.PropTypes.bool,
disabled: React.PropTypes.bool,
unread: React.PropTypes.bool,
mentions: React.PropTypes.number,
placement: React.PropTypes.oneOf(['left', 'right', 'top', 'bottom'])
};

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

@@ -0,0 +1,169 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import TeamButton from './components/team_button.jsx';
import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import $ from 'jquery';
import React from 'react';
import {FormattedMessage} from 'react-intl';
export default class TeamSidebar extends React.Component {
constructor(props) {
super(props);
this.getStateFromStores = this.getStateFromStores.bind(this);
this.onChange = this.onChange.bind(this);
this.handleResize = this.handleResize.bind(this);
this.setStyles = this.setStyles.bind(this);
this.state = this.getStateFromStores();
}
getStateFromStores() {
const teamMembers = TeamStore.getMyTeamMembers();
const currentTeamId = TeamStore.getCurrentId();
return {
teams: TeamStore.getAll(),
teamListings: TeamStore.getTeamListings(),
teamMembers,
currentTeamId,
show: teamMembers && teamMembers.length > 1
};
}
componentDidMount() {
window.addEventListener('resize', this.handleResize);
TeamStore.addChangeListener(this.onChange);
TeamStore.addUnreadChangeListener(this.onChange);
AsyncClient.getAllTeamListings();
this.setStyles();
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
TeamStore.removeChangeListener(this.onChange);
TeamStore.removeUnreadChangeListener(this.onChange);
}
componentDidUpdate(prevProps, prevState) {
$('.team-wrapper').perfectScrollbar();
// reset the scrollbar upon switching teams
if (this.state.currentTeam !== prevState.currentTeam) {
this.refs.container.scrollTop = 0;
$('.team-wrapper').perfectScrollbar('update');
}
}
onChange() {
this.setState(this.getStateFromStores());
this.setStyles();
}
handleResize() {
const teamMembers = this.state.teamMembers;
this.setState({show: teamMembers && teamMembers.length > 1});
this.setStyles();
}
setStyles() {
const root = document.querySelector('#root');
if (this.state.show) {
root.classList.add('multi-teams');
} else {
root.classList.remove('multi-teams');
}
}
render() {
if (!this.state.show) {
return null;
}
const myTeams = [];
const isSystemAdmin = Utils.isSystemAdmin(UserStore.getCurrentUser().roles);
const isAlreadyMember = new Map();
let moreTeams = false;
for (const index in this.state.teamMembers) {
if (this.state.teamMembers.hasOwnProperty(index)) {
const teamMember = this.state.teamMembers[index];
const teamId = teamMember.team_id;
myTeams.push(Object.assign({
unread: teamMember.msg_count > 0,
mentions: teamMember.mention_count
}, this.state.teams[teamId]));
isAlreadyMember[teamId] = true;
}
}
for (const id in this.state.teamListings) {
if (this.state.teamListings.hasOwnProperty(id) && !isAlreadyMember[id]) {
moreTeams = true;
break;
}
}
const teams = myTeams.
sort((a, b) => a.display_name.localeCompare(b.display_name)).
map((team) => {
return (
<TeamButton
key={'switch_team_' + team.name}
url={`/${team.name}`}
tip={team.display_name}
active={team.id === this.state.currentTeamId}
contents={team.display_name.substring(0, 1).toUpperCase()}
unread={team.unread}
mentions={team.mentions}
/>
);
});
if (moreTeams) {
teams.push(
<TeamButton
key='more_teams'
url='/select_team'
tip={
<FormattedMessage
id='team_sidebar.join'
defaultMessage='Other teams you can join.'
/>
}
contents={<i className='fa fa-plus'/>}
/>
);
} else if (global.window.mm_config.EnableTeamCreation === 'true' || isSystemAdmin) {
teams.push(
<TeamButton
key='more_teams'
url='/create_team'
tip={
<FormattedMessage
id='navbar_dropdown.create'
defaultMessage='Create a New Team'
/>
}
contents={<i className='fa fa-plus'/>}
/>
);
}
return (
<div className='team-sidebar'>
<div className='team-wrapper'>
{teams}
</div>
</div>
);
}
}

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

@@ -50,6 +50,7 @@ export default class SidebarRight extends React.Component {
$('.app__body .inner-wrap').removeClass('move--right'); $('.app__body .inner-wrap').removeClass('move--right');
$('.app__body .inner-wrap').addClass('webrtc--show'); $('.app__body .inner-wrap').addClass('webrtc--show');
$('.app__body .sidebar--left').removeClass('move--right'); $('.app__body .sidebar--left').removeClass('move--right');
$('.multi-teams .team-sidebar').removeClass('move--right');
$('.app__body .webrtc').addClass('webrtc--show'); $('.app__body .webrtc').addClass('webrtc--show');
//$('.sidebar--right').prepend('<div class="sidebar__overlay"></div>'); //$('.sidebar--right').prepend('<div class="sidebar__overlay"></div>');

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

@@ -1602,6 +1602,7 @@
"navbar_dropdown.help": "Help", "navbar_dropdown.help": "Help",
"navbar_dropdown.integrations": "Integrations", "navbar_dropdown.integrations": "Integrations",
"navbar_dropdown.inviteMember": "Invite New Member", "navbar_dropdown.inviteMember": "Invite New Member",
"navbar_dropdown.join": "Join Another Team",
"navbar_dropdown.leave": "Leave Team", "navbar_dropdown.leave": "Leave Team",
"navbar_dropdown.logout": "Logout", "navbar_dropdown.logout": "Logout",
"navbar_dropdown.manageMembers": "Manage Members", "navbar_dropdown.manageMembers": "Manage Members",
@@ -1750,7 +1751,6 @@
"sidebar_right_menu.nativeApps": "Download Apps", "sidebar_right_menu.nativeApps": "Download Apps",
"sidebar_right_menu.recentMentions": "Recent Mentions", "sidebar_right_menu.recentMentions": "Recent Mentions",
"sidebar_right_menu.report": "Report a Problem", "sidebar_right_menu.report": "Report a Problem",
"sidebar_right_menu.switch_team": "Team Selection",
"sidebar_right_menu.teamLink": "Get Team Invite Link", "sidebar_right_menu.teamLink": "Get Team Invite Link",
"sidebar_right_menu.teamSettings": "Team Settings", "sidebar_right_menu.teamSettings": "Team Settings",
"sidebar_right_menu.viewMembers": "View Members", "sidebar_right_menu.viewMembers": "View Members",
@@ -1760,7 +1760,6 @@
"signup.ldap": "AD/LDAP Credentials", "signup.ldap": "AD/LDAP Credentials",
"signup.office365": "Office 365", "signup.office365": "Office 365",
"signup.title": "Create an account with:", "signup.title": "Create an account with:",
"signup_team.choose": "Your teams: ",
"signup_team.createTeam": "Or Create a Team", "signup_team.createTeam": "Or Create a Team",
"signup_team.disabled": "Team creation has been disabled. Please contact an administrator for access.", "signup_team.disabled": "Team creation has been disabled. Please contact an administrator for access.",
"signup_team.join_open": "Teams you can join: ", "signup_team.join_open": "Teams you can join: ",
@@ -1853,6 +1852,7 @@
"team_settings_modal.generalTab": "General", "team_settings_modal.generalTab": "General",
"team_settings_modal.importTab": "Import", "team_settings_modal.importTab": "Import",
"team_settings_modal.title": "Team Settings", "team_settings_modal.title": "Team Settings",
"team_sidebar.join": "Other teams you can join.",
"textbox.bold": "**bold**", "textbox.bold": "**bold**",
"textbox.edit": "Edit message", "textbox.edit": "Edit message",
"textbox.help": "Help", "textbox.help": "Help",
@@ -2170,6 +2170,7 @@
"web.footer.privacy": "Privacy", "web.footer.privacy": "Privacy",
"web.footer.terms": "Terms", "web.footer.terms": "Terms",
"web.header.back": "Back", "web.header.back": "Back",
"web.header.logout": "Logout",
"web.root.signup_info": "All team communication in one place, searchable and accessible anywhere", "web.root.signup_info": "All team communication in one place, searchable and accessible anywhere",
"webrtc.busy": "{username} is busy.", "webrtc.busy": "{username} is busy.",
"webrtc.call": "Call", "webrtc.call": "Call",

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

@@ -14,6 +14,7 @@ const ActionTypes = Constants.ActionTypes;
import * as AsyncClient from 'utils/async_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx';
import Client from 'client/web_client.jsx'; import Client from 'client/web_client.jsx';
import ChannelStore from 'stores/channel_store.jsx'; import ChannelStore from 'stores/channel_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import emojiRoute from 'routes/route_emoji.jsx'; import emojiRoute from 'routes/route_emoji.jsx';
import integrationsRoute from 'routes/route_integrations.jsx'; import integrationsRoute from 'routes/route_integrations.jsx';
@@ -63,7 +64,7 @@ function preNeedsTeam(nextState, replace, callback) {
// First check to make sure you're in the current team // First check to make sure you're in the current team
// for the current url. // for the current url.
const teamName = nextState.params.team; const teamName = nextState.params.team;
var team = TeamStore.getByName(teamName); const team = TeamStore.getByName(teamName);
if (!team) { if (!team) {
browserHistory.push('/'); browserHistory.push('/');
@@ -75,9 +76,10 @@ function preNeedsTeam(nextState, replace, callback) {
TeamStore.saveMyTeam(team); TeamStore.saveMyTeam(team);
TeamStore.emitChange(); TeamStore.emitChange();
loadProfilesAndTeamMembersForDMSidebar(); loadProfilesAndTeamMembersForDMSidebar();
AsyncClient.getMyTeamsUnread();
AsyncClient.getMyChannelMembers(); AsyncClient.getMyChannelMembers();
var d1 = $.Deferred(); //eslint-disable-line new-cap const d1 = $.Deferred(); //eslint-disable-line new-cap
Client.getChannels( Client.getChannels(
(data) => { (data) => {
@@ -101,6 +103,20 @@ function preNeedsTeam(nextState, replace, callback) {
}); });
} }
function selectLastChannel(nextState, replace, callback) {
const team = TeamStore.getByName(nextState.params.team);
const channelId = PreferenceStore.get(team.id, 'channel');
const channel = ChannelStore.getChannelById(channelId);
let channelName = 'town-square';
if (channel) {
channelName = channel.name;
}
replace(`/${team.name}/channels/${channelName}`);
callback();
}
function onPermalinkEnter(nextState, replace, callback) { function onPermalinkEnter(nextState, replace, callback) {
const postId = nextState.params.postid; const postId = nextState.params.postid;
GlobalActions.emitPostFocusEvent( GlobalActions.emitPostFocusEvent(
@@ -112,7 +128,7 @@ function onPermalinkEnter(nextState, replace, callback) {
export default { export default {
path: ':team', path: ':team',
onEnter: preNeedsTeam, onEnter: preNeedsTeam,
indexRoute: {onEnter: (nextState, replace) => replace('/' + nextState.params.team + '/channels/town-square')}, indexRoute: {onEnter: selectLastChannel},
childRoutes: [ childRoutes: [
integrationsRoute, integrationsRoute,
emojiRoute, emojiRoute,
@@ -126,10 +142,11 @@ export default {
onEnter: onChannelEnter, onEnter: onChannelEnter,
getComponents: (location, callback) => { getComponents: (location, callback) => {
Promise.all([ Promise.all([
System.import('components/team_sidebar/team_sidebar_controller.jsx'),
System.import('components/sidebar.jsx'), System.import('components/sidebar.jsx'),
System.import('components/channel_view.jsx') System.import('components/channel_view.jsx')
]).then( ]).then(
(comarr) => callback(null, {sidebar: comarr[0].default, center: comarr[1].default}) (comarr) => callback(null, {team_sidebar: comarr[0].default, sidebar: comarr[1].default, center: comarr[2].default})
); );
} }
}, },
@@ -138,10 +155,11 @@ export default {
onEnter: onPermalinkEnter, onEnter: onPermalinkEnter,
getComponents: (location, callback) => { getComponents: (location, callback) => {
Promise.all([ Promise.all([
System.import('components/team_sidebar/team_sidebar_controller.jsx'),
System.import('components/sidebar.jsx'), System.import('components/sidebar.jsx'),
System.import('components/permalink_view.jsx') System.import('components/permalink_view.jsx')
]).then( ]).then(
(comarr) => callback(null, {sidebar: comarr[0].default, center: comarr[1].default}) (comarr) => callback(null, {team_sidebar: comarr[0].default, sidebar: comarr[1].default, center: comarr[2].default})
); );
} }
}, },
@@ -149,10 +167,11 @@ export default {
path: 'tutorial', path: 'tutorial',
getComponents: (location, callback) => { getComponents: (location, callback) => {
Promise.all([ Promise.all([
System.import('components/team_sidebar/team_sidebar_controller.jsx'),
System.import('components/sidebar.jsx'), System.import('components/sidebar.jsx'),
System.import('components/tutorial/tutorial_view.jsx') System.import('components/tutorial/tutorial_view.jsx')
]).then( ]).then(
(comarr) => callback(null, {sidebar: comarr[0].default, center: comarr[1].default}) (comarr) => callback(null, {team_sidebar: comarr[0].default, sidebar: comarr[1].default, center: comarr[2].default})
); );
} }
} }

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

@@ -10,4 +10,5 @@
@import 'sidebar-left'; @import 'sidebar-left';
@import 'sidebar-menu'; @import 'sidebar-menu';
@import 'sidebar-right'; @import 'sidebar-right';
@import 'team-sidebar';
@import 'webhooks'; @import 'webhooks';

90
webapp/sass/layout/_team-sidebar.scss Обычный файл
Просмотреть файл

@@ -0,0 +1,90 @@
.multi-teams {
.team-sidebar {
height: 100%;
left: 0;
position: fixed;
text-align: center;
width: 75px;
z-index: 5;
.team-wrapper {
background-color: rgba(0, 0, 0, 0.2);
height: 100%;
overflow: auto;
padding-top: 15px;
.team-container {
width: 100%;
text-align: center;
display: table;
margin-bottom: 10px;
position: relative;
.team-btn {
border: none;
height: 42px;
text-align: center;
font-weight: bold;
border-radius: 5px;
position: relative;
width: 42px;
background-color: rgba(0, 0, 0, 0.3);
display: table-cell;
vertical-align: middle;
.badge {
position: absolute;
top: -3px;
right: -6px;
font-size: 10px;
}
}
&.active {
.team-btn {
border: 1px solid #fff;
background-color: transparent;
}
}
&.active:before {
background: black;
content: '';
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 5px;
}
&.unread:before {
background: black;
border-radius: 50%;
content: '';
height: 5px;
left: 0;
position: absolute;
top: 18px;
width: 5px;
}
a.team-disabled {
cursor: not-allowed;
opacity: 0.5;
}
}
.team-container a:hover {
text-decoration: none;
}
}
}
.sidebar--left {
left: 75px;
}
.app__content {
margin-left: 295px;
}
}

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

@@ -1163,6 +1163,33 @@
.post-comments { .post-comments {
padding: 9px 21px 10px 10px !important; padding: 9px 21px 10px 10px !important;
} }
.multi-teams {
.team-sidebar {
width: 75px;
}
.app__content {
margin-left: 0;
}
.sidebar--left {
left: 0;
&.move--right {
left: 75px;
}
}
.team-sidebar {
display: none;
&.move--right {
display: block;
@include translate3d(0, 0, 0);
}
}
}
} }
@media screen and (max-width: 640px) { @media screen and (max-width: 640px) {
@@ -1493,6 +1520,20 @@
} }
@media screen and (max-width: 320px) { @media screen and (max-width: 320px) {
.multi-teams {
.team-sidebar {
width: 65px;
}
.sidebar--left {
width: 220px;
&.move--right {
left: 65px;
}
}
}
.post { .post {
.post__header { .post__header {
.col__name { .col__name {

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

@@ -215,6 +215,10 @@ class ChannelStoreClass extends EventEmitter {
return this.channels; return this.channels;
} }
getChannelById(id) {
return this.channels.filter((c) => c.id === id)[0];
}
storeMyChannelMember(channelMember) { storeMyChannelMember(channelMember) {
const members = Object.assign({}, this.getMyMembers()); const members = Object.assign({}, this.getMyMembers());
members[channelMember.channel_id] = channelMember; members[channelMember.channel_id] = channelMember;
@@ -348,7 +352,6 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => {
switch (action.type) { switch (action.type) {
case ActionTypes.CLICK_CHANNEL: case ActionTypes.CLICK_CHANNEL:
ChannelStore.setCurrentId(action.id); ChannelStore.setCurrentId(action.id);
ChannelStore.resetCounts(action.id);
ChannelStore.setPostMode(ChannelStore.POST_MODE_CHANNEL); ChannelStore.setPostMode(ChannelStore.POST_MODE_CHANNEL);
ChannelStore.emitChange(); ChannelStore.emitChange();
break; break;

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

@@ -45,7 +45,7 @@ class NotificationStoreClass extends EventEmitter {
} }
const teamId = msgProps.team_id; const teamId = msgProps.team_id;
const channel = ChannelStore.get(post.channel_id); let channel = ChannelStore.get(post.channel_id);
const user = UserStore.getCurrentUser(); const user = UserStore.getCurrentUser();
const member = ChannelStore.getMyMember(post.channel_id); const member = ChannelStore.getMyMember(post.channel_id);
@@ -72,6 +72,9 @@ class NotificationStoreClass extends EventEmitter {
let title = Utils.localizeMessage('channel_loader.posted', 'Posted'); let title = Utils.localizeMessage('channel_loader.posted', 'Posted');
if (!channel) { if (!channel) {
title = msgProps.channel_display_name; title = msgProps.channel_display_name;
channel = {
name: msgProps.channel_name
};
} else if (channel.type === Constants.DM_CHANNEL) { } else if (channel.type === Constants.DM_CHANNEL) {
title = Utils.localizeMessage('notification.dm', 'Direct Message'); title = Utils.localizeMessage('notification.dm', 'Direct Message');
} else { } else {

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

@@ -140,9 +140,12 @@ class PreferenceStore extends EventEmitter {
} }
this.emitChange(); this.emitChange();
break; break;
case ActionTypes.CLICK_CHANNEL:
this.setPreference(action.team_id, 'channel', action.id);
break;
} }
} }
} }
global.PreferenceStore = new PreferenceStore(); global.PreferenceStore = new PreferenceStore();
export default global.PreferenceStore; export default global.PreferenceStore;

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

@@ -10,6 +10,7 @@ const ActionTypes = Constants.ActionTypes;
const CHANGE_EVENT = 'change'; const CHANGE_EVENT = 'change';
const STATS_EVENT = 'stats'; const STATS_EVENT = 'stats';
const UNREAD_EVENT = 'unread';
var Utils; var Utils;
@@ -53,17 +54,31 @@ class TeamStoreClass extends EventEmitter {
this.removeListener(STATS_EVENT, callback); this.removeListener(STATS_EVENT, callback);
} }
emitUnreadChange() {
this.emit(UNREAD_EVENT);
}
addUnreadChangeListener(callback) {
this.on(UNREAD_EVENT, callback);
}
removeUnreadChangeListener(callback) {
this.removeListener(UNREAD_EVENT, callback);
}
get(id) { get(id) {
var c = this.getAll(); var c = this.getAll();
return c[id]; return c[id];
} }
getByName(name) { getByName(name) {
var t = this.getAll(); const t = this.getAll();
for (var id in t) { for (const id in t) {
if (t[id].name === name) { if (t.hasOwnProperty(id)) {
return t[id]; if (t[id].name === name) {
return t[id];
}
} }
} }
@@ -158,6 +173,25 @@ class TeamStoreClass extends EventEmitter {
this.teams = teams; this.teams = teams;
} }
updateTeam(team) {
const t = JSON.parse(team);
if (this.teams && this.teams[t.id]) {
this.teams[t.id] = t;
}
if (this.teamListings && this.teamListings[t.id]) {
if (t.allow_open_invite) {
this.teamListings[t.id] = t;
} else {
Reflect.deleteProperty(this.teamListings, t.id);
}
} else if (t.allow_open_invite) {
this.teamListings[t.id] = t;
}
this.emitChange();
}
saveMyTeam(team) { saveMyTeam(team) {
this.saveTeam(team); this.saveTeam(team);
this.currentTeamId = team.id; this.currentTeamId = team.id;
@@ -175,12 +209,29 @@ class TeamStoreClass extends EventEmitter {
this.my_team_members.push(member); this.my_team_members.push(member);
} }
saveMyTeamMembersUnread(members) {
for (let i = 0; i < this.my_team_members.length; i++) {
const team = this.my_team_members[i];
const member = members.filter((m) => m.team_id === team.team_id)[0];
if (member) {
this.my_team_members[i] = Object.assign({},
team,
{
msg_count: member.msg_count,
mention_count: member.mention_count
});
}
}
}
removeMyTeamMember(teamId) { removeMyTeamMember(teamId) {
for (let i = 0; i < this.my_team_members.length; i++) { for (let i = 0; i < this.my_team_members.length; i++) {
if (this.my_team_members[i].team_id === teamId) { if (this.my_team_members[i].team_id === teamId) {
this.my_team_members.splice(i, 1); this.my_team_members.splice(i, 1);
} }
} }
this.emitChange();
} }
getMyTeamMembers() { getMyTeamMembers() {
@@ -248,6 +299,14 @@ class TeamStoreClass extends EventEmitter {
return false; return false;
} }
updateUnreadCount(teamId, totalMsgCount, channelMember) {
const member = this.my_team_members.filter((m) => m.team_id === teamId)[0];
if (member) {
member.msg_count -= (totalMsgCount - channelMember.msg_count);
member.mention_count -= channelMember.mention_count;
}
}
} }
var TeamStore = new TeamStoreClass(); var TeamStore = new TeamStoreClass();
@@ -277,6 +336,10 @@ TeamStore.dispatchToken = AppDispatcher.register((payload) => {
TeamStore.saveMyTeamMembers(action.team_members); TeamStore.saveMyTeamMembers(action.team_members);
TeamStore.emitChange(); TeamStore.emitChange();
break; break;
case ActionTypes.RECEIVED_MY_TEAMS_UNREAD:
TeamStore.saveMyTeamMembersUnread(action.team_members);
TeamStore.emitChange();
break;
case ActionTypes.RECEIVED_ALL_TEAM_LISTINGS: case ActionTypes.RECEIVED_ALL_TEAM_LISTINGS:
TeamStore.saveTeamListings(action.teams); TeamStore.saveTeamListings(action.teams);
TeamStore.emitChange(); TeamStore.emitChange();
@@ -292,6 +355,12 @@ TeamStore.dispatchToken = AppDispatcher.register((payload) => {
TeamStore.saveStats(action.team_id, action.stats); TeamStore.saveStats(action.team_id, action.stats);
TeamStore.emitStatsChange(); TeamStore.emitStatsChange();
break; break;
case ActionTypes.CLICK_CHANNEL:
if (action.channelMember) {
TeamStore.updateUnreadCount(action.team_id, action.total_msg_count, action.channelMember);
TeamStore.emitUnreadChange();
}
break;
default: default:
} }
}); });

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

@@ -130,6 +130,20 @@ describe('Client.Team', function() {
}); });
}); });
it('getMyTeamMembers', function(done) {
TestHelper.initBasic(() => {
TestHelper.basicClient().getMyTeamMembers(
function(data) {
assert.equal(data.length > 0, true);
done();
},
function(err) {
done(new Error(err.message));
}
);
});
});
it('getTeamMembers', function(done) { it('getTeamMembers', function(done) {
TestHelper.initBasic(() => { TestHelper.initBasic(() => {
TestHelper.basicClient().getTeamMembers( TestHelper.basicClient().getTeamMembers(

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

@@ -166,12 +166,21 @@ export function updateLastViewedAt(id, active) {
channelId, channelId,
isActive, isActive,
() => { () => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PREFERENCE,
preference: {
category: 'last',
name: TeamStore.getCurrentId(),
value: channelId
}
});
callTracker[`updateLastViewed${channelId}`] = 0; callTracker[`updateLastViewed${channelId}`] = 0;
ErrorStore.clearLastError(); ErrorStore.clearLastError();
}, },
(err) => { (err) => {
callTracker[`updateLastViewed${channelId}`] = 0; callTracker[`updateLastViewed${channelId}`] = 0;
var count = ErrorStore.getConnectionErrorCount(); const count = ErrorStore.getConnectionErrorCount();
ErrorStore.setConnectionErrorCount(count + 1); ErrorStore.setConnectionErrorCount(count + 1);
dispatchError(err, 'updateLastViewedAt'); dispatchError(err, 'updateLastViewedAt');
} }
@@ -203,6 +212,14 @@ export function setLastViewedAt(lastViewedAt, id) {
channelId, channelId,
lastViewedAt, lastViewedAt,
() => { () => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PREFERENCE,
preference: {
category: 'last',
name: TeamStore.getCurrentId(),
value: channelId
}
});
callTracker[`setLastViewedAt${channelId}${lastViewedAt}`] = 0; callTracker[`setLastViewedAt${channelId}${lastViewedAt}`] = 0;
ErrorStore.clearLastError(); ErrorStore.clearLastError();
}, },
@@ -847,6 +864,61 @@ export function getTeamMember(teamId, userId) {
); );
} }
export function getMyTeamMembers() {
const callName = 'getMyTeamMembers';
if (isCallInProgress(callName)) {
return;
}
callTracker[callName] = utils.getTimestamp();
Client.getMyTeamMembers(
(data) => {
callTracker[callName] = 0;
const members = {};
for (const member of data) {
members[member.team_id] = member;
}
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_MY_TEAM_MEMBERS_UNREAD,
team_members: members
});
},
(err) => {
callTracker[callName] = 0;
dispatchError(err, 'getMyTeamMembers');
}
);
}
export function getMyTeamsUnread(teamId) {
const members = TeamStore.getMyTeamMembers();
if (members.length > 1) {
const callName = 'getMyTeamsUnread';
if (isCallInProgress(callName)) {
return;
}
callTracker[callName] = utils.getTimestamp();
Client.getMyTeamsUnread(
teamId,
(data) => {
callTracker[callName] = 0;
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_MY_TEAMS_UNREAD,
team_members: data
});
},
(err) => {
callTracker[callName] = 0;
dispatchError(err, 'getMyTeamsUnread');
}
);
}
}
export function getTeamStats(teamId) { export function getTeamStats(teamId) {
const callName = `getTeamStats${teamId}`; const callName = `getTeamStats${teamId}`;
if (isCallInProgress(callName)) { if (isCallInProgress(callName)) {

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

@@ -139,6 +139,7 @@ export const ActionTypes = keyMirror({
RECEIVED_ALL_TEAMS: null, RECEIVED_ALL_TEAMS: null,
RECEIVED_ALL_TEAM_LISTINGS: null, RECEIVED_ALL_TEAM_LISTINGS: null,
RECEIVED_MY_TEAM_MEMBERS: null, RECEIVED_MY_TEAM_MEMBERS: null,
RECEIVED_MY_TEAMS_UNREAD: null,
RECEIVED_MEMBERS_IN_TEAM: null, RECEIVED_MEMBERS_IN_TEAM: null,
RECEIVED_TEAM_STATS: null, RECEIVED_TEAM_STATS: null,
@@ -202,6 +203,7 @@ export const SocketEvents = {
DIRECT_ADDED: 'direct_added', DIRECT_ADDED: 'direct_added',
NEW_USER: 'new_user', NEW_USER: 'new_user',
LEAVE_TEAM: 'leave_team', LEAVE_TEAM: 'leave_team',
UPDATE_TEAM: 'update_team',
USER_ADDED: 'user_added', USER_ADDED: 'user_added',
USER_REMOVED: 'user_removed', USER_REMOVED: 'user_removed',
USER_UPDATED: 'user_updated', USER_UPDATED: 'user_updated',

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

@@ -104,7 +104,9 @@ export function notifyMe(title, body, channel, teamId, duration, silent) {
var notification = new Notification(title, {body, tag: body, icon: icon50, requireInteraction: notificationDuration === 0, silent}); var notification = new Notification(title, {body, tag: body, icon: icon50, requireInteraction: notificationDuration === 0, silent});
notification.onclick = () => { notification.onclick = () => {
window.focus(); window.focus();
if (channel) { if (channel && channel.type === Constants.DM_CHANNEL) {
browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name);
} else if (channel) {
browserHistory.push(TeamStore.getTeamUrl(teamId) + '/channels/' + channel.name); browserHistory.push(TeamStore.getTeamUrl(teamId) + '/channels/' + channel.name);
} else if (teamId) { } else if (teamId) {
browserHistory.push(TeamStore.getTeamUrl(teamId) + '/channels/town-square'); browserHistory.push(TeamStore.getTeamUrl(teamId) + '/channels/town-square');
@@ -491,6 +493,8 @@ export function applyTheme(theme) {
changeCss('.sidebar--left .nav li.active a:before, .app__body .modal .settings-modal .nav-pills>li.active a:before', 'background:' + theme.sidebarTextActiveBorder); changeCss('.sidebar--left .nav li.active a:before, .app__body .modal .settings-modal .nav-pills>li.active a:before', 'background:' + theme.sidebarTextActiveBorder);
changeCss('.sidebar--left .sidebar__divider:before', 'background:' + changeOpacity(theme.sidebarTextActiveBorder, 0.5)); changeCss('.sidebar--left .sidebar__divider:before', 'background:' + changeOpacity(theme.sidebarTextActiveBorder, 0.5));
changeCss('.sidebar--left .sidebar__divider', 'color:' + theme.sidebarTextActiveBorder); changeCss('.sidebar--left .sidebar__divider', 'color:' + theme.sidebarTextActiveBorder);
changeCss('.multi-teams .team-sidebar .team-wrapper .team-container.active:before', 'background:' + theme.sidebarTextActiveBorder);
changeCss('.multi-teams .team-sidebar .team-wrapper .team-container.unread:before', 'background:' + theme.sidebarTextActiveBorder);
} }
if (theme.sidebarTextActiveColor) { if (theme.sidebarTextActiveColor) {
@@ -501,13 +505,13 @@ export function applyTheme(theme) {
if (theme.sidebarHeaderBg) { if (theme.sidebarHeaderBg) {
changeCss('.sidebar--left .team__header, .app__body .sidebar--menu .team__header, .app__body .post-list__timestamp > div', 'background:' + theme.sidebarHeaderBg); changeCss('.sidebar--left .team__header, .app__body .sidebar--menu .team__header, .app__body .post-list__timestamp > div', 'background:' + theme.sidebarHeaderBg);
changeCss('.app__body .modal .modal-header', 'background:' + theme.sidebarHeaderBg); changeCss('.app__body .modal .modal-header', 'background:' + theme.sidebarHeaderBg);
changeCss('.app__body #navbar .navbar-default', 'background:' + theme.sidebarHeaderBg); changeCss('.app__body .multi-teams .team-sidebar, .app__body #navbar .navbar-default', 'background:' + theme.sidebarHeaderBg);
changeCss('@media(max-width: 768px){.app__body .search-bar__container', 'background:' + theme.sidebarHeaderBg); changeCss('@media(max-width: 768px){.app__body .search-bar__container', 'background:' + theme.sidebarHeaderBg);
changeCss('.app__body .attachment .attachment__container', 'border-left-color:' + theme.sidebarHeaderBg); changeCss('.app__body .attachment .attachment__container', 'border-left-color:' + theme.sidebarHeaderBg);
} }
if (theme.sidebarHeaderTextColor) { if (theme.sidebarHeaderTextColor) {
changeCss('.sidebar--left .team__header .header__info, .app__body .sidebar--menu .team__header .header__info, .app__body .post-list__timestamp > div', 'color:' + theme.sidebarHeaderTextColor); changeCss('.multi-teams .team-sidebar .team-wrapper .team-container .team-btn, .sidebar--left .team__header .header__info, .app__body .sidebar--menu .team__header .header__info, .app__body .post-list__timestamp > div', 'color:' + theme.sidebarHeaderTextColor);
changeCss('.app__body .sidebar-header-dropdown__icon', 'fill:' + theme.sidebarHeaderTextColor); changeCss('.app__body .sidebar-header-dropdown__icon', 'fill:' + theme.sidebarHeaderTextColor);
changeCss('.sidebar--left .team__header .user__name, .app__body .sidebar--menu .team__header .user__name', 'color:' + changeOpacity(theme.sidebarHeaderTextColor, 0.8)); changeCss('.sidebar--left .team__header .user__name, .app__body .sidebar--menu .team__header .user__name', 'color:' + changeOpacity(theme.sidebarHeaderTextColor, 0.8));
changeCss('.sidebar--left .team__header:hover .user__name, .app__body .sidebar--menu .team__header:hover .user__name', 'color:' + theme.sidebarHeaderTextColor); changeCss('.sidebar--left .team__header:hover .user__name, .app__body .sidebar--menu .team__header:hover .user__name', 'color:' + theme.sidebarHeaderTextColor);
@@ -534,11 +538,13 @@ export function applyTheme(theme) {
if (theme.mentionBj) { if (theme.mentionBj) {
changeCss('.sidebar--left .nav-pills__unread-indicator, .app__body .new-messages__button div', 'background:' + theme.mentionBj); changeCss('.sidebar--left .nav-pills__unread-indicator, .app__body .new-messages__button div', 'background:' + theme.mentionBj);
changeCss('.sidebar--left .badge', 'background:' + theme.mentionBj + '!important;'); changeCss('.sidebar--left .badge', 'background:' + theme.mentionBj + '!important;');
changeCss('.multi-teams .team-sidebar .team-wrapper .team-container .team-btn .badge', 'background:' + theme.mentionBj + '!important;');
} }
if (theme.mentionColor) { if (theme.mentionColor) {
changeCss('.sidebar--left .nav-pills__unread-indicator, .app__body .new-messages__button div', 'color:' + theme.mentionColor); changeCss('.sidebar--left .nav-pills__unread-indicator, .app__body .new-messages__button div', 'color:' + theme.mentionColor);
changeCss('.sidebar--left .badge', 'color:' + theme.mentionColor + '!important;'); changeCss('.sidebar--left .badge', 'color:' + theme.mentionColor + '!important;');
changeCss('.multi-teams .team-sidebar .team-wrapper .team-container .team-btn .badge', 'color:' + theme.mentionColor + '!important;');
} }
if (theme.centerChannelBg) { if (theme.centerChannelBg) {