PLT-4167 Team Sidebar (#4569)
* 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)
Этот коммит содержится в:
коммит произвёл
Joram Wilander
родитель
3ce2ce9dc8
Коммит
999d1553e1
@@ -846,14 +846,21 @@ func setLastViewedAt(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Srv.Store.Channel().SetLastViewedAt(id, c.Session.UserId, newLastViewedAt)
|
||||
|
||||
preference := model.Preference{
|
||||
chanPref := model.Preference{
|
||||
UserId: c.Session.UserId,
|
||||
Category: model.PREFERENCE_CATEGORY_LAST,
|
||||
Category: c.TeamId,
|
||||
Name: model.PREFERENCE_NAME_LAST_CHANNEL,
|
||||
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.Add("channel_id", id)
|
||||
@@ -901,14 +908,21 @@ func updateLastViewedAt(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
go clearPushNotification(c.Session.UserId, id)
|
||||
}
|
||||
|
||||
preference := model.Preference{
|
||||
chanPref := model.Preference{
|
||||
UserId: c.Session.UserId,
|
||||
Category: model.PREFERENCE_CATEGORY_LAST,
|
||||
Category: c.TeamId,
|
||||
Name: model.PREFERENCE_NAME_LAST_CHANNEL,
|
||||
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.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("channel_type", channel.Type)
|
||||
message.Add("channel_display_name", channel.DisplayName)
|
||||
message.Add("channel_name", channel.Name)
|
||||
message.Add("sender_name", senderUsername)
|
||||
message.Add("team_id", team.Id)
|
||||
|
||||
|
||||
37
api/team.go
37
api/team.go
@@ -32,6 +32,8 @@ func InitTeam() {
|
||||
BaseRoutes.Teams.Handle("/get_invite_info", ApiAppHandler(getInviteInfo)).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("/members", ApiUserRequired(getMyTeamMembers)).Methods("GET")
|
||||
BaseRoutes.Teams.Handle("/unread", ApiUserRequired(getMyTeamsUnread)).Methods("GET")
|
||||
|
||||
BaseRoutes.NeedTeam.Handle("/me", ApiUserRequired(getMyTeam)).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
|
||||
}
|
||||
|
||||
// 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)
|
||||
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) {
|
||||
for _, invite := range invites {
|
||||
if len(invite) > 0 {
|
||||
@@ -801,6 +834,10 @@ func updateTeam(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
oldTeam.Sanitize()
|
||||
|
||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_UPDATE_TEAM, "", "", "", nil)
|
||||
message.Add("team", oldTeam.ToJson())
|
||||
go Publish(message)
|
||||
|
||||
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) {
|
||||
th := Setup().InitBasic()
|
||||
|
||||
|
||||
@@ -4863,6 +4863,10 @@
|
||||
"id": "store.sql_team.get_teams_for_email.app_error",
|
||||
"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",
|
||||
"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.
|
||||
// Must be authenticated.
|
||||
func (c *Client) GetTeamMember(teamId string, userId string) (*Result, *AppError) {
|
||||
|
||||
@@ -33,6 +33,7 @@ const (
|
||||
|
||||
PREFERENCE_CATEGORY_LAST = "last"
|
||||
PREFERENCE_NAME_LAST_CHANNEL = "channel"
|
||||
PREFERENCE_NAME_LAST_TEAM = "team"
|
||||
|
||||
PREFERENCE_CATEGORY_NOTIFICATIONS = "notifications"
|
||||
PREFERENCE_NAME_EMAIL_INTERVAL = "email_interval"
|
||||
|
||||
@@ -16,6 +16,12 @@ type TeamMember struct {
|
||||
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 {
|
||||
b, err := json.Marshal(o)
|
||||
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 {
|
||||
|
||||
if len(o.TeamId) != 26 {
|
||||
|
||||
@@ -18,6 +18,7 @@ const (
|
||||
WEBSOCKET_EVENT_DIRECT_ADDED = "direct_added"
|
||||
WEBSOCKET_EVENT_NEW_USER = "new_user"
|
||||
WEBSOCKET_EVENT_LEAVE_TEAM = "leave_team"
|
||||
WEBSOCKET_EVENT_UPDATE_TEAM = "update_team"
|
||||
WEBSOCKET_EVENT_USER_ADDED = "user_added"
|
||||
WEBSOCKET_EVENT_USER_UPDATED = "user_updated"
|
||||
WEBSOCKET_EVENT_USER_REMOVED = "user_removed"
|
||||
|
||||
@@ -326,3 +326,25 @@ func (s SqlPreferenceStore) Delete(userId, category, name string) 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")
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
GetActiveMemberCount(teamId string) StoreChannel
|
||||
GetTeamsForUser(userId string) StoreChannel
|
||||
GetTeamsUnreadForUser(teamId, userId string) StoreChannel
|
||||
RemoveMember(teamId string, userId string) StoreChannel
|
||||
RemoveAllMembersByTeam(teamId string) StoreChannel
|
||||
RemoveAllMembersByUser(userId string) StoreChannel
|
||||
@@ -270,6 +271,7 @@ type PreferenceStore interface {
|
||||
GetCategory(userId string, category string) StoreChannel
|
||||
GetAll(userId string) StoreChannel
|
||||
Delete(userId, category, name string) StoreChannel
|
||||
DeleteCategory(userId string, category string) StoreChannel
|
||||
PermanentDeleteByUser(userId string) StoreChannel
|
||||
IsFeatureEnabled(feature, userId string) StoreChannel
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ export function emitChannelClickEvent(channel) {
|
||||
);
|
||||
}
|
||||
function switchToChannel(chan) {
|
||||
const channelMember = ChannelStore.getMyMember(chan.id);
|
||||
const getMyChannelMembersPromise = AsyncClient.getChannelMember(chan.id, UserStore.getCurrentId());
|
||||
|
||||
getMyChannelMembersPromise.then(() => {
|
||||
@@ -56,6 +57,9 @@ export function emitChannelClickEvent(channel) {
|
||||
type: ActionTypes.CLICK_CHANNEL,
|
||||
name: chan.name,
|
||||
id: chan.id,
|
||||
team_id: chan.team_id,
|
||||
total_msg_count: chan.total_msg_count,
|
||||
channelMember,
|
||||
prev: ChannelStore.getCurrentId()
|
||||
});
|
||||
}
|
||||
@@ -443,7 +447,7 @@ export function viewLoggedIn() {
|
||||
PostStore.clearPendingPosts();
|
||||
}
|
||||
|
||||
var lastTimeTypingSent = 0;
|
||||
let lastTimeTypingSent = 0;
|
||||
export function emitLocalUserTypingEvent(channelId, parentId) {
|
||||
const t = Date.now();
|
||||
if ((t - lastTimeTypingSent) > Constants.UPDATE_TYPING_MS) {
|
||||
@@ -534,3 +538,35 @@ export function emitBrowserFocus(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;
|
||||
|
||||
export function handleNewPost(post, msg) {
|
||||
const teamId = TeamStore.getCurrentId();
|
||||
|
||||
if (ChannelStore.getCurrentId() === post.channel_id) {
|
||||
if (window.isActive) {
|
||||
AsyncClient.updateLastViewedAt(null, false);
|
||||
} else {
|
||||
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) {
|
||||
AsyncClient.getChannel(post.channel_id);
|
||||
}
|
||||
}
|
||||
|
||||
var websocketMessageProps = null;
|
||||
let websocketMessageProps = null;
|
||||
if (msg) {
|
||||
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) {
|
||||
Client.getPost(
|
||||
post.channel_id,
|
||||
|
||||
@@ -6,6 +6,7 @@ import $ from 'jquery';
|
||||
import UserStore from 'stores/user_store.jsx';
|
||||
import TeamStore from 'stores/team_store.jsx';
|
||||
import PostStore from 'stores/post_store.jsx';
|
||||
import PreferenceStore from 'stores/preference_store.jsx';
|
||||
import ChannelStore from 'stores/channel_store.jsx';
|
||||
import BrowserStore from 'stores/browser_store.jsx';
|
||||
import ErrorStore from 'stores/error_store.jsx';
|
||||
@@ -122,6 +123,10 @@ function handleEvent(msg) {
|
||||
handleLeaveTeamEvent(msg);
|
||||
break;
|
||||
|
||||
case SocketEvents.UPDATE_TEAM:
|
||||
handleUpdateTeamEvent(msg);
|
||||
break;
|
||||
|
||||
case SocketEvents.USER_ADDED:
|
||||
handleUserAddedEvent(msg);
|
||||
break;
|
||||
@@ -229,21 +234,37 @@ function handleNewUserEvent(msg) {
|
||||
AsyncClient.getUser(msg.data.user_id);
|
||||
AsyncClient.getChannelStats();
|
||||
loadProfilesAndTeamMembersForDMSidebar();
|
||||
|
||||
if (msg.data.user_id === UserStore.getCurrentId()) {
|
||||
AsyncClient.getMyTeamMembers();
|
||||
}
|
||||
}
|
||||
|
||||
function handleLeaveTeamEvent(msg) {
|
||||
if (UserStore.getCurrentId() === msg.data.user_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) {
|
||||
TeamStore.setCurrentId('');
|
||||
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) {
|
||||
AsyncClient.getChannel(msg.broadcast.channel_id);
|
||||
loadProfilesAndTeamMembersForDMSidebar();
|
||||
|
||||
@@ -598,6 +598,30 @@ export default class Client {
|
||||
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) {
|
||||
request.
|
||||
post(`${this.getTeamNeededRoute(teamId)}/members/ids`).
|
||||
|
||||
@@ -50,13 +50,14 @@ export default class AdminNavbarDropdown extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
var teams = [];
|
||||
const teams = [];
|
||||
let switchTeams;
|
||||
|
||||
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)) {
|
||||
var teamMember = this.state.teamMembers[index];
|
||||
var team = this.state.teams[teamMember.team_id];
|
||||
const teamMember = this.state.teamMembers[index];
|
||||
const team = this.state.teams[teamMember.team_id];
|
||||
teams.push(
|
||||
<li key={'team_' + team.name}>
|
||||
<Link
|
||||
@@ -79,6 +80,20 @@ export default class AdminNavbarDropdown extends React.Component {
|
||||
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 (
|
||||
@@ -104,17 +119,7 @@ export default class AdminNavbarDropdown extends React.Component {
|
||||
role='menu'
|
||||
>
|
||||
{teams}
|
||||
<li>
|
||||
<Link
|
||||
to={'/select_team'}
|
||||
>
|
||||
<i className='fa fa-exchange'/>
|
||||
<FormattedMessage
|
||||
id='admin.nav.switch'
|
||||
defaultMessage='Team Selection'
|
||||
/>
|
||||
</Link>
|
||||
</li>
|
||||
{switchTeams}
|
||||
<li
|
||||
key='teamDiv'
|
||||
className='divider'
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
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 {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 (
|
||||
<div>
|
||||
<ErrorBar/>
|
||||
<div className='signup-header'>
|
||||
<Link to='/select_team'>
|
||||
<Link to={url}>
|
||||
<span className='fa fa-chevron-left'/>
|
||||
<FormattedMessage
|
||||
id='web.header.back'
|
||||
|
||||
@@ -54,7 +54,7 @@ export default class LoginController extends React.Component {
|
||||
document.title = global.window.mm_config.SiteName;
|
||||
|
||||
if (UserStore.getCurrentUser()) {
|
||||
browserHistory.push('/select_team');
|
||||
GlobalActions.redirectUserToDefaultTeam();
|
||||
}
|
||||
|
||||
AsyncClient.checkVersion();
|
||||
@@ -205,7 +205,7 @@ export default class LoginController extends React.Component {
|
||||
} else if (team) {
|
||||
browserHistory.push(`/${team.name}`);
|
||||
} 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') {
|
||||
$('.app__body .inner-wrap').removeClass('move--right move--left move--left-small');
|
||||
$('.app__body .sidebar--left').removeClass('move--right');
|
||||
$('.multi-teams .team-sidebar').removeClass('move--right');
|
||||
$('.app__body .sidebar--right').removeClass('move--left');
|
||||
$('.app__body .sidebar--menu').removeClass('move--left');
|
||||
}
|
||||
@@ -155,6 +156,7 @@ export default class Navbar extends React.Component {
|
||||
toggleLeftSidebar() {
|
||||
$('.app__body .inner-wrap').toggleClass('move--right');
|
||||
$('.app__body .sidebar--left').toggleClass('move--right');
|
||||
$('.multi-teams .team-sidebar').toggleClass('move--right');
|
||||
}
|
||||
|
||||
toggleRightSidebar() {
|
||||
|
||||
@@ -141,6 +141,7 @@ export default class NeedsTeam extends React.Component {
|
||||
content.push(
|
||||
this.props.navbar
|
||||
);
|
||||
content.push(this.props.team_sidebar);
|
||||
content.push(
|
||||
this.props.sidebar
|
||||
);
|
||||
@@ -197,6 +198,7 @@ NeedsTeam.propTypes = {
|
||||
]),
|
||||
navbar: React.PropTypes.element,
|
||||
sidebar: React.PropTypes.element,
|
||||
team_sidebar: React.PropTypes.element,
|
||||
center: React.PropTypes.element,
|
||||
params: React.PropTypes.object,
|
||||
user: React.PropTypes.object
|
||||
|
||||
@@ -3,14 +3,22 @@
|
||||
|
||||
import * as utils from 'utils/utils.jsx';
|
||||
import ChannelStore from 'stores/channel_store.jsx';
|
||||
import TeamStore from 'stores/team_store.jsx';
|
||||
|
||||
function getCountsStateFromStores() {
|
||||
var count = 0;
|
||||
var channels = ChannelStore.getAll();
|
||||
var members = ChannelStore.getMyMembers();
|
||||
let count = 0;
|
||||
const teamMembers = TeamStore.getMyTeamMembers();
|
||||
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) => {
|
||||
var channelMember = members[channel.id];
|
||||
const channelMember = members[channel.id];
|
||||
if (channelMember == null) {
|
||||
return;
|
||||
}
|
||||
@@ -41,10 +49,12 @@ export default class NotifyCounts extends React.Component {
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
ChannelStore.addChangeListener(this.onListenerChange);
|
||||
TeamStore.addChangeListener(this.onListenerChange);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
ChannelStore.removeChangeListener(this.onListenerChange);
|
||||
TeamStore.removeChangeListener(this.onListenerChange);
|
||||
}
|
||||
onListenerChange() {
|
||||
if (this.mounted) {
|
||||
|
||||
@@ -50,7 +50,7 @@ export default class Root extends React.Component {
|
||||
if (UserStore.getNoAccounts()) {
|
||||
browserHistory.push('/signup_user_complete');
|
||||
} else if (UserStore.getCurrentUser()) {
|
||||
browserHistory.push('/select_team');
|
||||
GlobalActions.redirectUserToDefaultTeam();
|
||||
} else {
|
||||
browserHistory.push('/login');
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ export default class SelectTeam extends React.Component {
|
||||
super(props);
|
||||
this.onTeamChange = this.onTeamChange.bind(this);
|
||||
this.handleTeamClick = this.handleTeamClick.bind(this);
|
||||
this.teamContentsCompare = this.teamContentsCompare.bind(this);
|
||||
|
||||
const state = this.getStateFromStores(false);
|
||||
state.loadingTeamId = '';
|
||||
@@ -61,33 +62,15 @@ export default class SelectTeam extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
let content = null;
|
||||
let teamContents = [];
|
||||
let openTeamContents = [];
|
||||
const isAlreadyMember = new Map();
|
||||
const isSystemAdmin = Utils.isSystemAdmin(UserStore.getCurrentUser().roles);
|
||||
let teamMembersCount = 0;
|
||||
|
||||
for (const teamMember of this.state.teamMembers) {
|
||||
const teamId = teamMember.team_id;
|
||||
const team = this.state.teams[teamId];
|
||||
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) {
|
||||
if (this.state.teamListings.hasOwnProperty(id) && !isAlreadyMember[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)) {
|
||||
teamContents = (
|
||||
if (openTeamContents.length === 0 && (global.window.mm_config.EnableTeamCreation === 'true' || isSystemAdmin)) {
|
||||
openTeamContents = (
|
||||
<div className='signup-team-dir-err'>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
@@ -114,8 +97,8 @@ export default class SelectTeam extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (teamMembersCount === 0 && teamContents.length === 0 && openTeamContents.length === 0) {
|
||||
teamContents = (
|
||||
} else if (openTeamContents.length === 0) {
|
||||
openTeamContents = (
|
||||
<div className='signup-team-dir-err'>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
@@ -125,42 +108,25 @@ export default class SelectTeam extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (teamContents.length === 0 && openTeamContents.length > 0) {
|
||||
teamContents = null;
|
||||
}
|
||||
|
||||
if (teamContents) {
|
||||
content = (
|
||||
<div className='signup__content'>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id='signup_team.choose'
|
||||
defaultMessage='Your teams:'
|
||||
/>
|
||||
</h4>
|
||||
<div className='signup-team-all'>
|
||||
{teamContents}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
if (Array.isArray(openTeamContents)) {
|
||||
openTeamContents = openTeamContents.sort(this.teamContentsCompare);
|
||||
}
|
||||
|
||||
var openContent;
|
||||
if (openTeamContents.length > 0) {
|
||||
openContent = (
|
||||
<div className='signup__content'>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id='signup_team.join_open'
|
||||
defaultMessage='Teams you can join: '
|
||||
/>
|
||||
</h4>
|
||||
<div className='signup-team-all'>
|
||||
{openTeamContents.sort(this.teamContentsCompare)}
|
||||
</div>
|
||||
let openContent = (
|
||||
<div className='signup__content'>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id='signup_team.join_open'
|
||||
defaultMessage='Teams you can join: '
|
||||
/>
|
||||
</h4>
|
||||
<div className='signup-team-all'>
|
||||
{openTeamContents}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!this.state.loaded) {
|
||||
openContent = <LoadingScreen/>;
|
||||
@@ -177,7 +143,7 @@ export default class SelectTeam extends React.Component {
|
||||
}
|
||||
|
||||
let teamSignUp;
|
||||
if (isSystemAdmin || (global.window.mm_config.EnableTeamCreation === 'true' && !UserAgent.isMobileApp())) {
|
||||
if (isSystemAdmin || global.window.mm_config.EnableTeamCreation === 'true') {
|
||||
teamSignUp = (
|
||||
<div className='margin--extra'>
|
||||
<Link
|
||||
@@ -197,7 +163,7 @@ export default class SelectTeam extends React.Component {
|
||||
}
|
||||
|
||||
let adminConsoleLink;
|
||||
if (isSystemAdmin) {
|
||||
if (isSystemAdmin && !UserAgent.isMobileApp()) {
|
||||
adminConsoleLink = (
|
||||
<div className='margin--extra hidden-xs'>
|
||||
<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 (
|
||||
<div>
|
||||
<ErrorBar/>
|
||||
<div className='signup-header'>
|
||||
<a
|
||||
href='#'
|
||||
onClick={GlobalActions.emitUserLoggedOutEvent}
|
||||
>
|
||||
<span className='fa fa-chevron-left'/>
|
||||
<FormattedMessage
|
||||
id='navbar_dropdown.logout'
|
||||
/>
|
||||
</a>
|
||||
{headerButton}
|
||||
</div>
|
||||
<div className='col-sm-12'>
|
||||
<div className={'signup-team__container'}>
|
||||
@@ -249,7 +226,6 @@ export default class SelectTeam extends React.Component {
|
||||
<h4 className='color--light'>
|
||||
{description}
|
||||
</h4>
|
||||
{content}
|
||||
{openContent}
|
||||
{teamSignUp}
|
||||
{adminConsoleLink}
|
||||
|
||||
@@ -82,11 +82,19 @@ export default class Sidebar extends React.Component {
|
||||
let msgs = 0;
|
||||
let mentions = 0;
|
||||
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) => {
|
||||
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;
|
||||
mentions += unreadCounts[chId].mentions;
|
||||
}
|
||||
@@ -97,6 +105,7 @@ export default class Sidebar extends React.Component {
|
||||
|
||||
getStateFromStores() {
|
||||
const members = ChannelStore.getMyMembers();
|
||||
const teamMembers = TeamStore.getMyTeamMembers();
|
||||
const currentChannelId = ChannelStore.getCurrentId();
|
||||
const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
|
||||
const channelList = ChannelUtils.buildDisplayableChannelList(Object.assign([], ChannelStore.getAll()));
|
||||
@@ -104,6 +113,7 @@ export default class Sidebar extends React.Component {
|
||||
return {
|
||||
activeId: currentChannelId,
|
||||
members,
|
||||
teamMembers,
|
||||
...channelList,
|
||||
unreadCounts: JSON.parse(JSON.stringify(ChannelStore.getUnreadCounts())),
|
||||
showTutorialTip: tutorialStep === TutorialSteps.CHANNEL_POPOVER,
|
||||
@@ -153,6 +163,7 @@ export default class Sidebar extends React.Component {
|
||||
if (this.state.activeId !== prevState.activeId) {
|
||||
$('.app__body .inner-wrap').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() {
|
||||
this.setState(this.getStateFromStores());
|
||||
this.updateTitle();
|
||||
}
|
||||
|
||||
updateTitle() {
|
||||
|
||||
@@ -45,7 +45,6 @@ export default class SidebarHeaderDropdown extends React.Component {
|
||||
this.showGetTeamInviteLinkModal = this.showGetTeamInviteLinkModal.bind(this);
|
||||
this.showTeamMembersModal = this.showTeamMembersModal.bind(this);
|
||||
this.hideTeamMembersModal = this.hideTeamMembersModal.bind(this);
|
||||
this.handleSwitchTeams = this.handleSwitchTeams.bind(this);
|
||||
|
||||
this.onTeamChange = this.onTeamChange.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.state = {
|
||||
teams: TeamStore.getAll(),
|
||||
teamMembers: TeamStore.getMyTeamMembers(),
|
||||
teamListings: TeamStore.getTeamListings(),
|
||||
showAboutModal: false,
|
||||
showDropdown: 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() {
|
||||
TeamStore.addChangeListener(this.onTeamChange);
|
||||
document.addEventListener('keydown', this.openAccountSettings);
|
||||
@@ -143,8 +137,8 @@ export default class SidebarHeaderDropdown extends React.Component {
|
||||
|
||||
onTeamChange() {
|
||||
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() {
|
||||
const config = global.mm_config;
|
||||
var teamLink = '';
|
||||
var inviteLink = '';
|
||||
var manageLink = '';
|
||||
var sysAdminLink = '';
|
||||
var currentUser = this.props.currentUser;
|
||||
var isAdmin = false;
|
||||
var isSystemAdmin = false;
|
||||
var teamSettings = null;
|
||||
const currentUser = this.props.currentUser;
|
||||
let teamLink = '';
|
||||
let inviteLink = '';
|
||||
let manageLink = '';
|
||||
let sysAdminLink = '';
|
||||
let isAdmin = false;
|
||||
let isSystemAdmin = false;
|
||||
let teamSettings = null;
|
||||
let integrationsLink = null;
|
||||
|
||||
if (!currentUser) {
|
||||
@@ -322,6 +316,7 @@ export default class SidebarHeaderDropdown extends React.Component {
|
||||
}
|
||||
|
||||
const teams = [];
|
||||
let moreTeams = false;
|
||||
|
||||
if (config.EnableTeamCreation === 'true') {
|
||||
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(
|
||||
<li key='leaveTeam_li'>
|
||||
<a
|
||||
@@ -354,39 +374,6 @@ export default class SidebarHeaderDropdown extends React.Component {
|
||||
</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;
|
||||
if (config.HelpLink) {
|
||||
helpLink = (
|
||||
|
||||
@@ -82,6 +82,7 @@ export default class SidebarRight extends React.Component {
|
||||
$('.app__body .inner-wrap').removeClass('.move--right');
|
||||
$('.app__body .inner-wrap').addClass('move--left');
|
||||
$('.app__body .sidebar--left').removeClass('move--right');
|
||||
$('.multi-teams .team-sidebar').removeClass('move--right');
|
||||
$('.app__body .sidebar--right').addClass('move--left');
|
||||
|
||||
//$('.sidebar--right').prepend('<div class="sidebar__overlay"></div>');
|
||||
|
||||
@@ -391,15 +391,6 @@ export default class SidebarRightMenu extends React.Component {
|
||||
{teamSettingsLink}
|
||||
{manageLink}
|
||||
{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'/>
|
||||
{helpLink}
|
||||
{reportLink}
|
||||
|
||||
@@ -110,7 +110,7 @@ export default class SignupEmail extends React.Component {
|
||||
if (query.redirect_to) {
|
||||
browserHistory.push(query.redirect_to);
|
||||
} else {
|
||||
browserHistory.push('/select_team');
|
||||
GlobalActions.redirectUserToDefaultTeam();
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -133,7 +133,7 @@ export default class SignupEmail extends React.Component {
|
||||
if (query.redirect_to) {
|
||||
browserHistory.push(query.redirect_to);
|
||||
} else {
|
||||
browserHistory.push('/select_team');
|
||||
GlobalActions.redirectUserToDefaultTeam();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -90,8 +90,13 @@ export default class SignupLdap extends React.Component {
|
||||
finishSignup() {
|
||||
GlobalActions.emitInitialLoad(
|
||||
() => {
|
||||
const query = this.props.location.query;
|
||||
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) {
|
||||
browserHistory.push('/select_team');
|
||||
GlobalActions.redirectUserToDefaultTeam();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
84
webapp/components/team_sidebar/components/team_button.jsx
Обычный файл
84
webapp/components/team_sidebar/components/team_button.jsx
Обычный файл
@@ -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'])
|
||||
};
|
||||
169
webapp/components/team_sidebar/team_sidebar_controller.jsx
Обычный файл
169
webapp/components/team_sidebar/team_sidebar_controller.jsx
Обычный файл
@@ -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').addClass('webrtc--show');
|
||||
$('.app__body .sidebar--left').removeClass('move--right');
|
||||
$('.multi-teams .team-sidebar').removeClass('move--right');
|
||||
$('.app__body .webrtc').addClass('webrtc--show');
|
||||
|
||||
//$('.sidebar--right').prepend('<div class="sidebar__overlay"></div>');
|
||||
|
||||
@@ -1602,6 +1602,7 @@
|
||||
"navbar_dropdown.help": "Help",
|
||||
"navbar_dropdown.integrations": "Integrations",
|
||||
"navbar_dropdown.inviteMember": "Invite New Member",
|
||||
"navbar_dropdown.join": "Join Another Team",
|
||||
"navbar_dropdown.leave": "Leave Team",
|
||||
"navbar_dropdown.logout": "Logout",
|
||||
"navbar_dropdown.manageMembers": "Manage Members",
|
||||
@@ -1750,7 +1751,6 @@
|
||||
"sidebar_right_menu.nativeApps": "Download Apps",
|
||||
"sidebar_right_menu.recentMentions": "Recent Mentions",
|
||||
"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.teamSettings": "Team Settings",
|
||||
"sidebar_right_menu.viewMembers": "View Members",
|
||||
@@ -1760,7 +1760,6 @@
|
||||
"signup.ldap": "AD/LDAP Credentials",
|
||||
"signup.office365": "Office 365",
|
||||
"signup.title": "Create an account with:",
|
||||
"signup_team.choose": "Your teams: ",
|
||||
"signup_team.createTeam": "Or Create a Team",
|
||||
"signup_team.disabled": "Team creation has been disabled. Please contact an administrator for access.",
|
||||
"signup_team.join_open": "Teams you can join: ",
|
||||
@@ -1853,6 +1852,7 @@
|
||||
"team_settings_modal.generalTab": "General",
|
||||
"team_settings_modal.importTab": "Import",
|
||||
"team_settings_modal.title": "Team Settings",
|
||||
"team_sidebar.join": "Other teams you can join.",
|
||||
"textbox.bold": "**bold**",
|
||||
"textbox.edit": "Edit message",
|
||||
"textbox.help": "Help",
|
||||
@@ -2170,6 +2170,7 @@
|
||||
"web.footer.privacy": "Privacy",
|
||||
"web.footer.terms": "Terms",
|
||||
"web.header.back": "Back",
|
||||
"web.header.logout": "Logout",
|
||||
"web.root.signup_info": "All team communication in one place, searchable and accessible anywhere",
|
||||
"webrtc.busy": "{username} is busy.",
|
||||
"webrtc.call": "Call",
|
||||
|
||||
@@ -14,6 +14,7 @@ const ActionTypes = Constants.ActionTypes;
|
||||
import * as AsyncClient from 'utils/async_client.jsx';
|
||||
import Client from 'client/web_client.jsx';
|
||||
import ChannelStore from 'stores/channel_store.jsx';
|
||||
import PreferenceStore from 'stores/preference_store.jsx';
|
||||
|
||||
import emojiRoute from 'routes/route_emoji.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
|
||||
// for the current url.
|
||||
const teamName = nextState.params.team;
|
||||
var team = TeamStore.getByName(teamName);
|
||||
const team = TeamStore.getByName(teamName);
|
||||
|
||||
if (!team) {
|
||||
browserHistory.push('/');
|
||||
@@ -75,9 +76,10 @@ function preNeedsTeam(nextState, replace, callback) {
|
||||
TeamStore.saveMyTeam(team);
|
||||
TeamStore.emitChange();
|
||||
loadProfilesAndTeamMembersForDMSidebar();
|
||||
AsyncClient.getMyTeamsUnread();
|
||||
AsyncClient.getMyChannelMembers();
|
||||
|
||||
var d1 = $.Deferred(); //eslint-disable-line new-cap
|
||||
const d1 = $.Deferred(); //eslint-disable-line new-cap
|
||||
|
||||
Client.getChannels(
|
||||
(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) {
|
||||
const postId = nextState.params.postid;
|
||||
GlobalActions.emitPostFocusEvent(
|
||||
@@ -112,7 +128,7 @@ function onPermalinkEnter(nextState, replace, callback) {
|
||||
export default {
|
||||
path: ':team',
|
||||
onEnter: preNeedsTeam,
|
||||
indexRoute: {onEnter: (nextState, replace) => replace('/' + nextState.params.team + '/channels/town-square')},
|
||||
indexRoute: {onEnter: selectLastChannel},
|
||||
childRoutes: [
|
||||
integrationsRoute,
|
||||
emojiRoute,
|
||||
@@ -126,10 +142,11 @@ export default {
|
||||
onEnter: onChannelEnter,
|
||||
getComponents: (location, callback) => {
|
||||
Promise.all([
|
||||
System.import('components/team_sidebar/team_sidebar_controller.jsx'),
|
||||
System.import('components/sidebar.jsx'),
|
||||
System.import('components/channel_view.jsx')
|
||||
]).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,
|
||||
getComponents: (location, callback) => {
|
||||
Promise.all([
|
||||
System.import('components/team_sidebar/team_sidebar_controller.jsx'),
|
||||
System.import('components/sidebar.jsx'),
|
||||
System.import('components/permalink_view.jsx')
|
||||
]).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',
|
||||
getComponents: (location, callback) => {
|
||||
Promise.all([
|
||||
System.import('components/team_sidebar/team_sidebar_controller.jsx'),
|
||||
System.import('components/sidebar.jsx'),
|
||||
System.import('components/tutorial/tutorial_view.jsx')
|
||||
]).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-menu';
|
||||
@import 'sidebar-right';
|
||||
@import 'team-sidebar';
|
||||
@import 'webhooks';
|
||||
|
||||
90
webapp/sass/layout/_team-sidebar.scss
Обычный файл
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 {
|
||||
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) {
|
||||
@@ -1493,6 +1520,20 @@
|
||||
}
|
||||
|
||||
@media screen and (max-width: 320px) {
|
||||
.multi-teams {
|
||||
.team-sidebar {
|
||||
width: 65px;
|
||||
}
|
||||
|
||||
.sidebar--left {
|
||||
width: 220px;
|
||||
|
||||
&.move--right {
|
||||
left: 65px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.post {
|
||||
.post__header {
|
||||
.col__name {
|
||||
|
||||
@@ -215,6 +215,10 @@ class ChannelStoreClass extends EventEmitter {
|
||||
return this.channels;
|
||||
}
|
||||
|
||||
getChannelById(id) {
|
||||
return this.channels.filter((c) => c.id === id)[0];
|
||||
}
|
||||
|
||||
storeMyChannelMember(channelMember) {
|
||||
const members = Object.assign({}, this.getMyMembers());
|
||||
members[channelMember.channel_id] = channelMember;
|
||||
@@ -348,7 +352,6 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.CLICK_CHANNEL:
|
||||
ChannelStore.setCurrentId(action.id);
|
||||
ChannelStore.resetCounts(action.id);
|
||||
ChannelStore.setPostMode(ChannelStore.POST_MODE_CHANNEL);
|
||||
ChannelStore.emitChange();
|
||||
break;
|
||||
|
||||
@@ -45,7 +45,7 @@ class NotificationStoreClass extends EventEmitter {
|
||||
}
|
||||
const teamId = msgProps.team_id;
|
||||
|
||||
const channel = ChannelStore.get(post.channel_id);
|
||||
let channel = ChannelStore.get(post.channel_id);
|
||||
const user = UserStore.getCurrentUser();
|
||||
const member = ChannelStore.getMyMember(post.channel_id);
|
||||
|
||||
@@ -72,6 +72,9 @@ class NotificationStoreClass extends EventEmitter {
|
||||
let title = Utils.localizeMessage('channel_loader.posted', 'Posted');
|
||||
if (!channel) {
|
||||
title = msgProps.channel_display_name;
|
||||
channel = {
|
||||
name: msgProps.channel_name
|
||||
};
|
||||
} else if (channel.type === Constants.DM_CHANNEL) {
|
||||
title = Utils.localizeMessage('notification.dm', 'Direct Message');
|
||||
} else {
|
||||
|
||||
@@ -140,9 +140,12 @@ class PreferenceStore extends EventEmitter {
|
||||
}
|
||||
this.emitChange();
|
||||
break;
|
||||
case ActionTypes.CLICK_CHANNEL:
|
||||
this.setPreference(action.team_id, 'channel', action.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 STATS_EVENT = 'stats';
|
||||
const UNREAD_EVENT = 'unread';
|
||||
|
||||
var Utils;
|
||||
|
||||
@@ -53,17 +54,31 @@ class TeamStoreClass extends EventEmitter {
|
||||
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) {
|
||||
var c = this.getAll();
|
||||
return c[id];
|
||||
}
|
||||
|
||||
getByName(name) {
|
||||
var t = this.getAll();
|
||||
const t = this.getAll();
|
||||
|
||||
for (var id in t) {
|
||||
if (t[id].name === name) {
|
||||
return t[id];
|
||||
for (const id in t) {
|
||||
if (t.hasOwnProperty(id)) {
|
||||
if (t[id].name === name) {
|
||||
return t[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +173,25 @@ class TeamStoreClass extends EventEmitter {
|
||||
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) {
|
||||
this.saveTeam(team);
|
||||
this.currentTeamId = team.id;
|
||||
@@ -175,12 +209,29 @@ class TeamStoreClass extends EventEmitter {
|
||||
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) {
|
||||
for (let i = 0; i < this.my_team_members.length; i++) {
|
||||
if (this.my_team_members[i].team_id === teamId) {
|
||||
this.my_team_members.splice(i, 1);
|
||||
}
|
||||
}
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
getMyTeamMembers() {
|
||||
@@ -248,6 +299,14 @@ class TeamStoreClass extends EventEmitter {
|
||||
|
||||
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();
|
||||
@@ -277,6 +336,10 @@ TeamStore.dispatchToken = AppDispatcher.register((payload) => {
|
||||
TeamStore.saveMyTeamMembers(action.team_members);
|
||||
TeamStore.emitChange();
|
||||
break;
|
||||
case ActionTypes.RECEIVED_MY_TEAMS_UNREAD:
|
||||
TeamStore.saveMyTeamMembersUnread(action.team_members);
|
||||
TeamStore.emitChange();
|
||||
break;
|
||||
case ActionTypes.RECEIVED_ALL_TEAM_LISTINGS:
|
||||
TeamStore.saveTeamListings(action.teams);
|
||||
TeamStore.emitChange();
|
||||
@@ -292,6 +355,12 @@ TeamStore.dispatchToken = AppDispatcher.register((payload) => {
|
||||
TeamStore.saveStats(action.team_id, action.stats);
|
||||
TeamStore.emitStatsChange();
|
||||
break;
|
||||
case ActionTypes.CLICK_CHANNEL:
|
||||
if (action.channelMember) {
|
||||
TeamStore.updateUnreadCount(action.team_id, action.total_msg_count, action.channelMember);
|
||||
TeamStore.emitUnreadChange();
|
||||
}
|
||||
break;
|
||||
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) {
|
||||
TestHelper.initBasic(() => {
|
||||
TestHelper.basicClient().getTeamMembers(
|
||||
|
||||
@@ -166,12 +166,21 @@ export function updateLastViewedAt(id, active) {
|
||||
channelId,
|
||||
isActive,
|
||||
() => {
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_PREFERENCE,
|
||||
preference: {
|
||||
category: 'last',
|
||||
name: TeamStore.getCurrentId(),
|
||||
value: channelId
|
||||
}
|
||||
});
|
||||
|
||||
callTracker[`updateLastViewed${channelId}`] = 0;
|
||||
ErrorStore.clearLastError();
|
||||
},
|
||||
(err) => {
|
||||
callTracker[`updateLastViewed${channelId}`] = 0;
|
||||
var count = ErrorStore.getConnectionErrorCount();
|
||||
const count = ErrorStore.getConnectionErrorCount();
|
||||
ErrorStore.setConnectionErrorCount(count + 1);
|
||||
dispatchError(err, 'updateLastViewedAt');
|
||||
}
|
||||
@@ -203,6 +212,14 @@ export function setLastViewedAt(lastViewedAt, id) {
|
||||
channelId,
|
||||
lastViewedAt,
|
||||
() => {
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_PREFERENCE,
|
||||
preference: {
|
||||
category: 'last',
|
||||
name: TeamStore.getCurrentId(),
|
||||
value: channelId
|
||||
}
|
||||
});
|
||||
callTracker[`setLastViewedAt${channelId}${lastViewedAt}`] = 0;
|
||||
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) {
|
||||
const callName = `getTeamStats${teamId}`;
|
||||
if (isCallInProgress(callName)) {
|
||||
|
||||
@@ -139,6 +139,7 @@ export const ActionTypes = keyMirror({
|
||||
RECEIVED_ALL_TEAMS: null,
|
||||
RECEIVED_ALL_TEAM_LISTINGS: null,
|
||||
RECEIVED_MY_TEAM_MEMBERS: null,
|
||||
RECEIVED_MY_TEAMS_UNREAD: null,
|
||||
RECEIVED_MEMBERS_IN_TEAM: null,
|
||||
RECEIVED_TEAM_STATS: null,
|
||||
|
||||
@@ -202,6 +203,7 @@ export const SocketEvents = {
|
||||
DIRECT_ADDED: 'direct_added',
|
||||
NEW_USER: 'new_user',
|
||||
LEAVE_TEAM: 'leave_team',
|
||||
UPDATE_TEAM: 'update_team',
|
||||
USER_ADDED: 'user_added',
|
||||
USER_REMOVED: 'user_removed',
|
||||
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});
|
||||
notification.onclick = () => {
|
||||
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);
|
||||
} else if (teamId) {
|
||||
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 .sidebar__divider:before', 'background:' + changeOpacity(theme.sidebarTextActiveBorder, 0.5));
|
||||
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) {
|
||||
@@ -501,13 +505,13 @@ export function applyTheme(theme) {
|
||||
if (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 #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('.app__body .attachment .attachment__container', 'border-left-color:' + theme.sidebarHeaderBg);
|
||||
}
|
||||
|
||||
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('.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);
|
||||
@@ -534,11 +538,13 @@ export function applyTheme(theme) {
|
||||
if (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('.multi-teams .team-sidebar .team-wrapper .team-container .team-btn .badge', 'background:' + theme.mentionBj + '!important;');
|
||||
}
|
||||
|
||||
if (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('.multi-teams .team-sidebar .team-wrapper .team-container .team-btn .badge', 'color:' + theme.mentionColor + '!important;');
|
||||
}
|
||||
|
||||
if (theme.centerChannelBg) {
|
||||
|
||||
Ссылка в новой задаче
Block a user