Merge branch 'master' of https://github.com/mattermost/platform into ui-improvements
Этот коммит содержится в:
@@ -24,7 +24,7 @@ func InitAdmin(r *mux.Router) {
|
|||||||
sr.Handle("/config", ApiUserRequired(getConfig)).Methods("GET")
|
sr.Handle("/config", ApiUserRequired(getConfig)).Methods("GET")
|
||||||
sr.Handle("/save_config", ApiUserRequired(saveConfig)).Methods("POST")
|
sr.Handle("/save_config", ApiUserRequired(saveConfig)).Methods("POST")
|
||||||
sr.Handle("/test_email", ApiUserRequired(testEmail)).Methods("POST")
|
sr.Handle("/test_email", ApiUserRequired(testEmail)).Methods("POST")
|
||||||
sr.Handle("/client_props", ApiAppHandler(getClientProperties)).Methods("GET")
|
sr.Handle("/client_props", ApiAppHandler(getClientConfig)).Methods("GET")
|
||||||
sr.Handle("/log_client", ApiAppHandler(logClient)).Methods("POST")
|
sr.Handle("/log_client", ApiAppHandler(logClient)).Methods("POST")
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -57,8 +57,8 @@ func getLogs(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write([]byte(model.ArrayToJson(lines)))
|
w.Write([]byte(model.ArrayToJson(lines)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getClientProperties(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getClientConfig(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
w.Write([]byte(model.MapToJson(utils.ClientProperties)))
|
w.Write([]byte(model.MapToJson(utils.ClientCfg)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func logClient(c *Context, w http.ResponseWriter, r *http.Request) {
|
func logClient(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func NewServerTemplatePage(templateName string) *ServerTemplatePage {
|
|||||||
return &ServerTemplatePage{
|
return &ServerTemplatePage{
|
||||||
TemplateName: templateName,
|
TemplateName: templateName,
|
||||||
Props: make(map[string]string),
|
Props: make(map[string]string),
|
||||||
ClientProps: utils.ClientProperties,
|
ClientCfg: utils.ClientCfg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -131,16 +131,21 @@ func CreateDirectChannel(c *Context, otherUserId string) (*model.Channel, *model
|
|||||||
return nil, model.NewAppError("CreateDirectChannel", "Invalid other user id ", otherUserId)
|
return nil, model.NewAppError("CreateDirectChannel", "Invalid other user id ", otherUserId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sc, err := CreateChannel(c, channel, true); err != nil {
|
cm1 := &model.ChannelMember{
|
||||||
return nil, err
|
UserId: c.Session.UserId,
|
||||||
|
Roles: model.CHANNEL_ROLE_ADMIN,
|
||||||
|
NotifyProps: model.GetDefaultChannelNotifyProps(),
|
||||||
|
}
|
||||||
|
cm2 := &model.ChannelMember{
|
||||||
|
UserId: otherUserId,
|
||||||
|
Roles: "",
|
||||||
|
NotifyProps: model.GetDefaultChannelNotifyProps(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-Srv.Store.Channel().SaveDirectChannel(channel, cm1, cm2); result.Err != nil {
|
||||||
|
return nil, result.Err
|
||||||
} else {
|
} else {
|
||||||
cm := &model.ChannelMember{ChannelId: sc.Id, UserId: otherUserId, Roles: "", NotifyProps: model.GetDefaultChannelNotifyProps()}
|
return result.Data.(*model.Channel), nil
|
||||||
|
|
||||||
if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil {
|
|
||||||
return nil, cmresult.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
return sc, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
147
api/context.go
147
api/context.go
@@ -8,6 +8,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
l4g "code.google.com/p/log4go"
|
l4g "code.google.com/p/log4go"
|
||||||
@@ -19,20 +20,24 @@ import (
|
|||||||
var sessionCache *utils.Cache = utils.NewLru(model.SESSION_CACHE_SIZE)
|
var sessionCache *utils.Cache = utils.NewLru(model.SESSION_CACHE_SIZE)
|
||||||
|
|
||||||
type Context struct {
|
type Context struct {
|
||||||
Session model.Session
|
Session model.Session
|
||||||
RequestId string
|
RequestId string
|
||||||
IpAddress string
|
IpAddress string
|
||||||
Path string
|
Path string
|
||||||
Err *model.AppError
|
Err *model.AppError
|
||||||
teamURLValid bool
|
teamURLValid bool
|
||||||
teamURL string
|
teamURL string
|
||||||
siteURL string
|
siteURL string
|
||||||
|
SessionTokenIndex int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type Page struct {
|
type Page struct {
|
||||||
TemplateName string
|
TemplateName string
|
||||||
Props map[string]string
|
Props map[string]string
|
||||||
ClientProps map[string]string
|
ClientCfg map[string]string
|
||||||
|
User *model.User
|
||||||
|
Team *model.Team
|
||||||
|
SessionTokenIndex int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func ApiAppHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
func ApiAppHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
||||||
@@ -96,8 +101,37 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Attempt to parse the token from the cookie
|
// Attempt to parse the token from the cookie
|
||||||
if len(token) == 0 {
|
if len(token) == 0 {
|
||||||
if cookie, err := r.Cookie(model.SESSION_TOKEN); err == nil {
|
tokens := GetMultiSessionCookieTokens(r)
|
||||||
token = cookie.Value
|
if len(tokens) > 0 {
|
||||||
|
// If there is only 1 token in the cookie then just use it like normal
|
||||||
|
if len(tokens) == 1 {
|
||||||
|
token = tokens[0]
|
||||||
|
} else {
|
||||||
|
// If it is a multi-session token then find the correct session
|
||||||
|
sessionTokenIndexStr := r.URL.Query().Get(model.SESSION_TOKEN_INDEX)
|
||||||
|
sessionTokenIndex := int64(-1)
|
||||||
|
if len(sessionTokenIndexStr) > 0 {
|
||||||
|
if index, err := strconv.ParseInt(sessionTokenIndexStr, 10, 64); err == nil {
|
||||||
|
sessionTokenIndex = index
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sessionTokenIndexStr := r.Header.Get(model.HEADER_MM_SESSION_TOKEN_INDEX)
|
||||||
|
if len(sessionTokenIndexStr) > 0 {
|
||||||
|
if index, err := strconv.ParseInt(sessionTokenIndexStr, 10, 64); err == nil {
|
||||||
|
sessionTokenIndex = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sessionTokenIndex >= 0 && sessionTokenIndex < int64(len(tokens)) {
|
||||||
|
token = tokens[sessionTokenIndex]
|
||||||
|
c.SessionTokenIndex = sessionTokenIndex
|
||||||
|
} else {
|
||||||
|
c.SessionTokenIndex = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.SessionTokenIndex = -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,18 +157,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(token) != 0 {
|
if len(token) != 0 {
|
||||||
var session *model.Session
|
session := GetSession(token)
|
||||||
if ts, ok := sessionCache.Get(token); ok {
|
|
||||||
session = ts.(*model.Session)
|
|
||||||
}
|
|
||||||
|
|
||||||
if session == nil {
|
|
||||||
if sessionResult := <-Srv.Store.Session().Get(token); sessionResult.Err != nil {
|
|
||||||
c.LogError(model.NewAppError("ServeHTTP", "Invalid session", "token="+token+", err="+sessionResult.Err.DetailedError))
|
|
||||||
} else {
|
|
||||||
session = sessionResult.Data.(*model.Session)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if session == nil || session.IsExpired() {
|
if session == nil || session.IsExpired() {
|
||||||
c.RemoveSessionCookie(w, r)
|
c.RemoveSessionCookie(w, r)
|
||||||
@@ -318,10 +341,23 @@ func (c *Context) IsTeamAdmin() bool {
|
|||||||
|
|
||||||
func (c *Context) RemoveSessionCookie(w http.ResponseWriter, r *http.Request) {
|
func (c *Context) RemoveSessionCookie(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
sessionCache.Remove(c.Session.Token)
|
// multiToken := ""
|
||||||
|
// if oldMultiCookie, err := r.Cookie(model.SESSION_COOKIE_TOKEN); err == nil {
|
||||||
|
// multiToken = oldMultiCookie.Value
|
||||||
|
// }
|
||||||
|
|
||||||
|
// multiCookie := &http.Cookie{
|
||||||
|
// Name: model.SESSION_COOKIE_TOKEN,
|
||||||
|
// Value: strings.TrimSpace(strings.Replace(multiToken, c.Session.Token, "", -1)),
|
||||||
|
// Path: "/",
|
||||||
|
// MaxAge: model.SESSION_TIME_WEB_IN_SECS,
|
||||||
|
// HttpOnly: true,
|
||||||
|
// }
|
||||||
|
|
||||||
|
//http.SetCookie(w, multiCookie)
|
||||||
|
|
||||||
cookie := &http.Cookie{
|
cookie := &http.Cookie{
|
||||||
Name: model.SESSION_TOKEN,
|
Name: model.SESSION_COOKIE_TOKEN,
|
||||||
Value: "",
|
Value: "",
|
||||||
Path: "/",
|
Path: "/",
|
||||||
MaxAge: -1,
|
MaxAge: -1,
|
||||||
@@ -329,21 +365,6 @@ func (c *Context) RemoveSessionCookie(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
http.SetCookie(w, cookie)
|
http.SetCookie(w, cookie)
|
||||||
|
|
||||||
multiToken := ""
|
|
||||||
if oldMultiCookie, err := r.Cookie(model.MULTI_SESSION_TOKEN); err == nil {
|
|
||||||
multiToken = oldMultiCookie.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
multiCookie := &http.Cookie{
|
|
||||||
Name: model.MULTI_SESSION_TOKEN,
|
|
||||||
Value: strings.TrimSpace(strings.Replace(multiToken, c.Session.Token, "", -1)),
|
|
||||||
Path: "/",
|
|
||||||
MaxAge: model.SESSION_TIME_WEB_IN_SECS,
|
|
||||||
HttpOnly: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
http.SetCookie(w, multiCookie)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) SetInvalidParam(where string, name string) {
|
func (c *Context) SetInvalidParam(where string, name string) {
|
||||||
@@ -479,7 +500,7 @@ func RenderWebError(err *model.AppError, w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(err.StatusCode)
|
w.WriteHeader(err.StatusCode)
|
||||||
ServerTemplates.ExecuteTemplate(w, "error.html", Page{Props: props, ClientProps: utils.ClientProperties})
|
ServerTemplates.ExecuteTemplate(w, "error.html", Page{Props: props, ClientCfg: utils.ClientCfg})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Handle404(w http.ResponseWriter, r *http.Request) {
|
func Handle404(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -489,6 +510,46 @@ func Handle404(w http.ResponseWriter, r *http.Request) {
|
|||||||
RenderWebError(err, w, r)
|
RenderWebError(err, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetSession(token string) *model.Session {
|
||||||
|
var session *model.Session
|
||||||
|
if ts, ok := sessionCache.Get(token); ok {
|
||||||
|
session = ts.(*model.Session)
|
||||||
|
}
|
||||||
|
|
||||||
|
if session == nil {
|
||||||
|
if sessionResult := <-Srv.Store.Session().Get(token); sessionResult.Err != nil {
|
||||||
|
l4g.Error("Invalid session token=" + token + ", err=" + sessionResult.Err.DetailedError)
|
||||||
|
} else {
|
||||||
|
session = sessionResult.Data.(*model.Session)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return session
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMultiSessionCookieTokens(r *http.Request) []string {
|
||||||
|
if multiCookie, err := r.Cookie(model.SESSION_COOKIE_TOKEN); err == nil {
|
||||||
|
multiToken := multiCookie.Value
|
||||||
|
|
||||||
|
if len(multiToken) > 0 {
|
||||||
|
return strings.Split(multiToken, " ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindMultiSessionForTeamId(r *http.Request, teamId string) (int64, *model.Session) {
|
||||||
|
for index, token := range GetMultiSessionCookieTokens(r) {
|
||||||
|
s := GetSession(token)
|
||||||
|
if s != nil && !s.IsExpired() && s.TeamId == teamId {
|
||||||
|
return int64(index), s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1, nil
|
||||||
|
}
|
||||||
|
|
||||||
func AddSessionToCache(session *model.Session) {
|
func AddSessionToCache(session *model.Session) {
|
||||||
sessionCache.Add(session.Token, session)
|
sessionCache.Add(session.Token, session)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ func handleWebhookEventsAndForget(c *Context, post *model.Post, team *model.Team
|
|||||||
|
|
||||||
// copy the context and create a mock session for posting the message
|
// copy the context and create a mock session for posting the message
|
||||||
mockSession := model.Session{UserId: hook.CreatorId, TeamId: hook.TeamId, IsOAuth: false}
|
mockSession := model.Session{UserId: hook.CreatorId, TeamId: hook.TeamId, IsOAuth: false}
|
||||||
newContext := &Context{mockSession, model.NewId(), "", c.Path, nil, c.teamURLValid, c.teamURL, c.siteURL}
|
newContext := &Context{mockSession, model.NewId(), "", c.Path, nil, c.teamURLValid, c.teamURL, c.siteURL, 0}
|
||||||
|
|
||||||
if text, ok := respProps["text"]; ok {
|
if text, ok := respProps["text"]; ok {
|
||||||
if _, err := CreateWebhookPost(newContext, post.ChannelId, text, respProps["username"], respProps["icon_url"]); err != nil {
|
if _, err := CreateWebhookPost(newContext, post.ChannelId, text, respProps["username"], respProps["icon_url"]); err != nil {
|
||||||
|
|||||||
@@ -426,9 +426,9 @@ func emailTeams(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
subjectPage := NewServerTemplatePage("find_teams_subject")
|
subjectPage := NewServerTemplatePage("find_teams_subject")
|
||||||
subjectPage.ClientProps["SiteURL"] = c.GetSiteURL()
|
subjectPage.ClientCfg["SiteURL"] = c.GetSiteURL()
|
||||||
bodyPage := NewServerTemplatePage("find_teams_body")
|
bodyPage := NewServerTemplatePage("find_teams_body")
|
||||||
bodyPage.ClientProps["SiteURL"] = c.GetSiteURL()
|
bodyPage.ClientCfg["SiteURL"] = c.GetSiteURL()
|
||||||
|
|
||||||
if result := <-Srv.Store.Team().GetTeamsForEmail(email); result.Err != nil {
|
if result := <-Srv.Store.Team().GetTeamsForEmail(email); result.Err != nil {
|
||||||
c.Err = result.Err
|
c.Err = result.Err
|
||||||
|
|||||||
@@ -23,9 +23,9 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
|
<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
|
||||||
Any questions at all, mail us any time: <a href="mailto:{{.ClientProps.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientProps.FeedbackEmail}}</a>.<br>
|
Any questions at all, mail us any time: <a href="mailto:{{.ClientCfg.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientCfg.FeedbackEmail}}</a>.<br>
|
||||||
Best wishes,<br>
|
Best wishes,<br>
|
||||||
The {{.ClientProps.SiteName}} Team<br>
|
The {{.ClientCfg.SiteName}} Team<br>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p style="padding: 0 50px;">
|
<p style="padding: 0 50px;">
|
||||||
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
||||||
If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientProps.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
|
If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientCfg.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{{define "email_change_subject"}}[{{.ClientProps.SiteName}}] Your email address has changed for {{.Props.TeamDisplayName}}{{end}}
|
{{define "email_change_subject"}}[{{.ClientCfg.SiteName}}] Your email address has changed for {{.Props.TeamDisplayName}}{{end}}
|
||||||
|
|||||||
@@ -26,9 +26,9 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
|
<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
|
||||||
Any questions at all, mail us any time: <a href="mailto:{{.ClientProps.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientProps.FeedbackEmail}}</a>.<br>
|
Any questions at all, mail us any time: <a href="mailto:{{.ClientCfg.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientCfg.FeedbackEmail}}</a>.<br>
|
||||||
Best wishes,<br>
|
Best wishes,<br>
|
||||||
The {{.ClientProps.SiteName}} Team<br>
|
The {{.ClientCfg.SiteName}} Team<br>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p style="padding: 0 50px;">
|
<p style="padding: 0 50px;">
|
||||||
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
||||||
If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientProps.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
|
If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientCfg.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{{define "email_change_verify_subject"}}[{{.ClientProps.SiteName}}] Verify new email address for {{.Props.TeamDisplayName}}{{end}}
|
{{define "email_change_verify_subject"}}[{{.ClientCfg.SiteName}}] Verify new email address for {{.Props.TeamDisplayName}}{{end}}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
<title>{{ .ClientProps.SiteName }} - Error</title>
|
<title>{{ .ClientCfg.SiteName }} - Error</title>
|
||||||
|
|
||||||
<link rel="stylesheet" href="/static/css/bootstrap-3.3.5.min.css">
|
<link rel="stylesheet" href="/static/css/bootstrap-3.3.5.min.css">
|
||||||
<link rel="stylesheet" href="/static/css/jasny-bootstrap.min.css" rel="stylesheet">
|
<link rel="stylesheet" href="/static/css/jasny-bootstrap.min.css" rel="stylesheet">
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="error__container">
|
<div class="error__container">
|
||||||
<div class="error__icon"><i class="fa fa-exclamation-triangle"></i></div>
|
<div class="error__icon"><i class="fa fa-exclamation-triangle"></i></div>
|
||||||
<h2>{{ .ClientProps.SiteName }} needs your help:</h2>
|
<h2>{{ .ClientCfg.SiteName }} needs your help:</h2>
|
||||||
<p>{{ .Props.Message }}</p>
|
<p>{{ .Props.Message }}</p>
|
||||||
<a href="{{.Props.SiteURL}}">Go back to team site</a>
|
<a href="{{.Props.SiteURL}}">Go back to team site</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
|
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 20px 20px 10px; text-align:left;">
|
<td style="padding: 20px 20px 10px; text-align:left;">
|
||||||
<img src="{{.ClientProps.SiteURL}}/static/images/logo-email.png" width="130px" style="opacity: 0.5" alt="">
|
<img src="{{.ClientCfg.SiteURL}}/static/images/logo-email.png" width="130px" style="opacity: 0.5" alt="">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -31,9 +31,9 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
|
<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
|
||||||
Any questions at all, mail us any time: <a href="mailto:{{.ClientProps.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientProps.FeedbackEmail}}</a>.<br>
|
Any questions at all, mail us any time: <a href="mailto:{{.ClientCfg.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientCfg.FeedbackEmail}}</a>.<br>
|
||||||
Best wishes,<br>
|
Best wishes,<br>
|
||||||
The {{.ClientProps.SiteName}} Team<br>
|
The {{.ClientCfg.SiteName}} Team<br>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -42,11 +42,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
|
<td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
|
||||||
<p style="margin: 25px 0;">
|
<p style="margin: 25px 0;">
|
||||||
<img width="65" src="{{.ClientProps.SiteURL}}/static/images/circles.png" alt="">
|
<img width="65" src="{{.ClientCfg.SiteURL}}/static/images/circles.png" alt="">
|
||||||
</p>
|
</p>
|
||||||
<p style="padding: 0 50px;">
|
<p style="padding: 0 50px;">
|
||||||
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
||||||
If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientProps.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
|
If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientCfg.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{{define "find_teams_subject"}}Your {{ .ClientProps.SiteName }} Teams{{end}}
|
{{define "find_teams_subject"}}Your {{ .ClientCfg.SiteName }} Teams{{end}}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
|
<td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
|
||||||
<h2 style="font-weight: normal; margin-top: 10px;">You've been invited</h2>
|
<h2 style="font-weight: normal; margin-top: 10px;">You've been invited</h2>
|
||||||
<p>{{.Props.TeamDisplayName}} started using {{.ClientProps.SiteName}}.<br> The team {{.Props.SenderStatus}} <strong>{{.Props.SenderName}}</strong>, has invited you to join <strong>{{.Props.TeamDisplayName}}</strong>.</p>
|
<p>{{.Props.TeamDisplayName}} started using {{.ClientCfg.SiteName}}.<br> The team {{.Props.SenderStatus}} <strong>{{.Props.SenderName}}</strong>, has invited you to join <strong>{{.Props.TeamDisplayName}}</strong>.</p>
|
||||||
<p style="margin: 20px 0 15px">
|
<p style="margin: 20px 0 15px">
|
||||||
<a href="{{.Props.Link}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">Join Team</a>
|
<a href="{{.Props.Link}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">Join Team</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -26,9 +26,9 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
|
<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
|
||||||
Any questions at all, mail us any time: <a href="mailto:{{.ClientProps.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientProps.FeedbackEmail}}</a>.<br>
|
Any questions at all, mail us any time: <a href="mailto:{{.ClientCfg.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientCfg.FeedbackEmail}}</a>.<br>
|
||||||
Best wishes,<br>
|
Best wishes,<br>
|
||||||
The {{.ClientProps.SiteName}} Team<br>
|
The {{.ClientCfg.SiteName}} Team<br>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p style="padding: 0 50px;">
|
<p style="padding: 0 50px;">
|
||||||
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
||||||
If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientProps.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
|
If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientCfg.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{{define "invite_subject"}}{{ .Props.SenderName }} invited you to join {{ .Props.TeamDisplayName }} Team on {{.ClientProps.SiteName}}{{end}}
|
{{define "invite_subject"}}{{ .Props.SenderName }} invited you to join {{ .Props.TeamDisplayName }} Team on {{.ClientCfg.SiteName}}{{end}}
|
||||||
|
|||||||
@@ -23,9 +23,9 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
|
<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
|
||||||
Any questions at all, mail us any time: <a href="mailto:{{.ClientProps.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientProps.FeedbackEmail}}</a>.<br>
|
Any questions at all, mail us any time: <a href="mailto:{{.ClientCfg.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientCfg.FeedbackEmail}}</a>.<br>
|
||||||
Best wishes,<br>
|
Best wishes,<br>
|
||||||
The {{.ClientProps.SiteName}} Team<br>
|
The {{.ClientCfg.SiteName}} Team<br>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p style="padding: 0 50px;">
|
<p style="padding: 0 50px;">
|
||||||
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
||||||
If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientProps.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
|
If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientCfg.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{{define "password_change_subject"}}You updated your password for {{.Props.TeamDisplayName}} on {{ .ClientProps.SiteName }}{{end}}
|
{{define "password_change_subject"}}You updated your password for {{.Props.TeamDisplayName}} on {{ .ClientCfg.SiteName }}{{end}}
|
||||||
|
|||||||
@@ -26,9 +26,9 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
|
<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
|
||||||
Any questions at all, mail us any time: <a href="mailto:{{.ClientProps.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientProps.FeedbackEmail}}</a>.<br>
|
Any questions at all, mail us any time: <a href="mailto:{{.ClientCfg.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientCfg.FeedbackEmail}}</a>.<br>
|
||||||
Best wishes,<br>
|
Best wishes,<br>
|
||||||
The {{.ClientProps.SiteName}} Team<br>
|
The {{.ClientCfg.SiteName}} Team<br>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p style="padding: 0 50px;">
|
<p style="padding: 0 50px;">
|
||||||
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
||||||
If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientProps.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
|
If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientCfg.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{{define "post_subject"}}[{{.ClientProps.SiteName}}] {{.Props.TeamDisplayName}} Team Notifications for {{.Props.Month}} {{.Props.Day}}, {{.Props.Year}}{{end}}
|
{{define "post_subject"}}[{{.ClientCfg.SiteName}}] {{.Props.TeamDisplayName}} Team Notifications for {{.Props.Month}} {{.Props.Day}}, {{.Props.Year}}{{end}}
|
||||||
|
|||||||
@@ -26,9 +26,9 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
|
<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
|
||||||
Any questions at all, mail us any time: <a href="mailto:{{.ClientProps.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientProps.FeedbackEmail}}</a>.<br>
|
Any questions at all, mail us any time: <a href="mailto:{{.ClientCfg.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientCfg.FeedbackEmail}}</a>.<br>
|
||||||
Best wishes,<br>
|
Best wishes,<br>
|
||||||
The {{.ClientProps.SiteName}} Team<br>
|
The {{.ClientCfg.SiteName}} Team<br>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p style="padding: 0 50px;">
|
<p style="padding: 0 50px;">
|
||||||
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
||||||
If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientProps.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
|
If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientCfg.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -21,14 +21,14 @@
|
|||||||
<p style="margin: 20px 0 25px">
|
<p style="margin: 20px 0 25px">
|
||||||
<a href="{{.Props.Link}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">Set up your team</a>
|
<a href="{{.Props.Link}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">Set up your team</a>
|
||||||
</p>
|
</p>
|
||||||
{{ .ClientProps.SiteName }} is one place for all your team communication, searchable and available anywhere.<br>You'll get more out of {{ .ClientProps.SiteName }} when your team is in constant communication--let's get them on board.<br></p>
|
{{ .ClientCfg.SiteName }} is one place for all your team communication, searchable and available anywhere.<br>You'll get more out of {{ .ClientCfg.SiteName }} when your team is in constant communication--let's get them on board.<br></p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
|
<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
|
||||||
Any questions at all, mail us any time: <a href="mailto:{{.ClientProps.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientProps.FeedbackEmail}}</a>.<br>
|
Any questions at all, mail us any time: <a href="mailto:{{.ClientCfg.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientCfg.FeedbackEmail}}</a>.<br>
|
||||||
Best wishes,<br>
|
Best wishes,<br>
|
||||||
The {{.ClientProps.SiteName}} Team<br>
|
The {{.ClientCfg.SiteName}} Team<br>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p style="padding: 0 50px;">
|
<p style="padding: 0 50px;">
|
||||||
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
||||||
If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientProps.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
|
If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientCfg.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{{define "signup_team_subject"}}Invitation to {{ .ClientProps.SiteName }}{{end}}
|
{{define "signup_team_subject"}}Invitation to {{ .ClientCfg.SiteName }}{{end}}
|
||||||
@@ -26,9 +26,9 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
|
<td style="color: #999; padding-top: 20px; line-height: 25px; font-size: 13px;">
|
||||||
Any questions at all, mail us any time: <a href="mailto:{{.ClientProps.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientProps.FeedbackEmail}}</a>.<br>
|
Any questions at all, mail us any time: <a href="mailto:{{.ClientCfg.FeedbackEmail}}" style="text-decoration: none; color:#2389D7;">{{.ClientCfg.FeedbackEmail}}</a>.<br>
|
||||||
Best wishes,<br>
|
Best wishes,<br>
|
||||||
The {{.ClientProps.SiteName}} Team<br>
|
The {{.ClientCfg.SiteName}} Team<br>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p style="padding: 0 50px;">
|
<p style="padding: 0 50px;">
|
||||||
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
||||||
If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientProps.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
|
If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientCfg.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{{define "verify_subject"}}[{{ .Props.TeamDisplayName }} {{ .ClientProps.SiteName }}] Email Verification{{end}}
|
{{define "verify_subject"}}[{{ .Props.TeamDisplayName }} {{ .ClientCfg.SiteName }}] Email Verification{{end}}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p style="padding: 0 50px;">
|
<p style="padding: 0 50px;">
|
||||||
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
(c) 2015 Mattermost, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.<br>
|
||||||
If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientProps.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
|
If you no longer wish to receive these emails, click on the following link: <a href="mailto:{{.ClientCfg.FeedbackEmail}}?subject=Unsubscribe&body=Unsubscribe" style="text-decoration: none; color:#2389D7;">Unsubscribe</a>
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
50
api/user.go
50
api/user.go
@@ -428,43 +428,23 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User,
|
|||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set(model.HEADER_TOKEN, session.Token)
|
w.Header().Set(model.HEADER_TOKEN, session.Token)
|
||||||
sessionCookie := &http.Cookie{
|
|
||||||
Name: model.SESSION_TOKEN,
|
|
||||||
Value: session.Token,
|
|
||||||
Path: "/",
|
|
||||||
MaxAge: maxAge,
|
|
||||||
HttpOnly: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
http.SetCookie(w, sessionCookie)
|
|
||||||
|
|
||||||
|
tokens := GetMultiSessionCookieTokens(r)
|
||||||
multiToken := ""
|
multiToken := ""
|
||||||
if originalMultiSessionCookie, err := r.Cookie(model.MULTI_SESSION_TOKEN); err == nil {
|
seen := make(map[string]string)
|
||||||
multiToken = originalMultiSessionCookie.Value
|
seen[session.TeamId] = session.TeamId
|
||||||
}
|
for _, token := range tokens {
|
||||||
|
s := GetSession(token)
|
||||||
// Attempt to clean all the old tokens or duplicate tokens
|
if s != nil && !s.IsExpired() && seen[s.TeamId] == "" {
|
||||||
if len(multiToken) > 0 {
|
multiToken += " " + token
|
||||||
tokens := strings.Split(multiToken, " ")
|
seen[s.TeamId] = s.TeamId
|
||||||
|
|
||||||
multiToken = ""
|
|
||||||
seen := make(map[string]string)
|
|
||||||
seen[session.TeamId] = session.TeamId
|
|
||||||
for _, token := range tokens {
|
|
||||||
if sr := <-Srv.Store.Session().Get(token); sr.Err == nil {
|
|
||||||
s := sr.Data.(*model.Session)
|
|
||||||
if !s.IsExpired() && seen[s.TeamId] == "" {
|
|
||||||
multiToken += " " + token
|
|
||||||
seen[s.TeamId] = s.TeamId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
multiToken = strings.TrimSpace(session.Token + " " + multiToken)
|
multiToken = strings.TrimSpace(multiToken + " " + session.Token)
|
||||||
|
|
||||||
multiSessionCookie := &http.Cookie{
|
multiSessionCookie := &http.Cookie{
|
||||||
Name: model.MULTI_SESSION_TOKEN,
|
Name: model.SESSION_COOKIE_TOKEN,
|
||||||
Value: multiToken,
|
Value: multiToken,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
MaxAge: maxAge,
|
MaxAge: maxAge,
|
||||||
@@ -1241,6 +1221,11 @@ func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
user = result.Data.(*model.User)
|
user = result.Data.(*model.User)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(user.AuthData) != 0 {
|
||||||
|
c.Err = model.NewAppError("sendPasswordReset", "Cannot reset password for SSO accounts", "userId="+user.Id+", teamId="+team.Id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
newProps := make(map[string]string)
|
newProps := make(map[string]string)
|
||||||
newProps["user_id"] = user.Id
|
newProps["user_id"] = user.Id
|
||||||
newProps["time"] = fmt.Sprintf("%v", model.GetMillis())
|
newProps["time"] = fmt.Sprintf("%v", model.GetMillis())
|
||||||
@@ -1325,6 +1310,11 @@ func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
user = result.Data.(*model.User)
|
user = result.Data.(*model.User)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(user.AuthData) != 0 {
|
||||||
|
c.Err = model.NewAppError("resetPassword", "Cannot reset password for SSO accounts", "userId="+user.Id+", teamId="+team.Id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if user.TeamId != team.Id {
|
if user.TeamId != team.Id {
|
||||||
c.Err = model.NewAppError("resetPassword", "Trying to reset password for user on wrong team.", "userId="+user.Id+", teamId="+team.Id)
|
c.Err = model.NewAppError("resetPassword", "Trying to reset password for user on wrong team.", "userId="+user.Id+", teamId="+team.Id)
|
||||||
c.Err.StatusCode = http.StatusForbidden
|
c.Err.StatusCode = http.StatusForbidden
|
||||||
|
|||||||
@@ -817,6 +817,16 @@ func TestSendPasswordReset(t *testing.T) {
|
|||||||
if _, err := Client.SendPasswordReset(data); err == nil {
|
if _, err := Client.SendPasswordReset(data); err == nil {
|
||||||
t.Fatal("Should have errored - bad name")
|
t.Fatal("Should have errored - bad name")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", AuthData: "1", AuthService: "random"}
|
||||||
|
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
|
||||||
|
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
|
||||||
|
|
||||||
|
data["email"] = user2.Email
|
||||||
|
data["name"] = team.Name
|
||||||
|
if _, err := Client.SendPasswordReset(data); err == nil {
|
||||||
|
t.Fatal("should have errored - SSO user can't send reset password link")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResetPassword(t *testing.T) {
|
func TestResetPassword(t *testing.T) {
|
||||||
@@ -901,6 +911,20 @@ func TestResetPassword(t *testing.T) {
|
|||||||
if _, err := Client.ResetPassword(data); err == nil {
|
if _, err := Client.ResetPassword(data); err == nil {
|
||||||
t.Fatal("Should have errored - domain team doesn't match user team")
|
t.Fatal("Should have errored - domain team doesn't match user team")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", AuthData: "1", AuthService: "random"}
|
||||||
|
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
|
||||||
|
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
|
||||||
|
|
||||||
|
data["new_password"] = "newpwd"
|
||||||
|
props["user_id"] = user2.Id
|
||||||
|
props["time"] = fmt.Sprintf("%v", model.GetMillis())
|
||||||
|
data["data"] = model.MapToJson(props)
|
||||||
|
data["hash"] = model.HashPassword(fmt.Sprintf("%v:%v", data["data"], utils.Cfg.EmailSettings.PasswordResetSalt))
|
||||||
|
data["name"] = team.Name
|
||||||
|
if _, err := Client.ResetPassword(data); err == nil {
|
||||||
|
t.Fatal("should have errored - SSO user can't reset password")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUserUpdateNotify(t *testing.T) {
|
func TestUserUpdateNotify(t *testing.T) {
|
||||||
|
|||||||
35
doc/developer/API.md
Обычный файл
35
doc/developer/API.md
Обычный файл
@@ -0,0 +1,35 @@
|
|||||||
|
# Mattermost APIs
|
||||||
|
|
||||||
|
Mattermost APIs let you integrate your favorite tools and services withing your Mattermost experience.
|
||||||
|
|
||||||
|
## Slack-compatible integration support
|
||||||
|
|
||||||
|
To offer an alternative to propreitary SaaS services, Mattermost focuses on being "Slack-compatible, but not Slack limited". That means providing support for developers of Slack applications to easily extend their apps to Mattermost, as well as support and capabilities beyond what Slack offers.
|
||||||
|
|
||||||
|
### [Incoming Webhooks](https://github.com/mattermost/platform/blob/master/doc/integrations/webhooks/Incoming-Webhooks.md)
|
||||||
|
|
||||||
|
Incoming webhooks allow external applications to post messages into Mattermost channels and private groups by sending a JSON payload via HTTP POST request to a secret Mattermost URL generated specifically for each application.
|
||||||
|
|
||||||
|
In addition to supporting Slack's incoming webhook formatting, Mattermost webhooks offer full support of industry-standard markdown formatting, including headings, tables and in-line images.
|
||||||
|
|
||||||
|
### Outgoing Webhooks (coming in Mattermost v1.2)
|
||||||
|
|
||||||
|
Outgoing webhooks allow external applications to receive webhook events from events happening within Mattermost channels and private groups via JSON payloads via HTTP POST requests sent to incoming webhook URLs defined by your applications.
|
||||||
|
|
||||||
|
Over time, Mattermost outgoing webhooks will support not only Slack applications using a compatible format, but also offer optional events and triggers beyond Slack's feature set.
|
||||||
|
|
||||||
|
## Mattermost Drivers
|
||||||
|
|
||||||
|
Mattermost is written in Golang and React and designed as a self-hosted system, which differs from Slack's technical platform and focus on SaaS. Therefore the Mattermost drivers will differ from Slack's interfaces.
|
||||||
|
|
||||||
|
Another key difference is that as an open source project, you are welcome to access and use Mattermost's APIs on your installations the same way the core team would use them for buildling new features.
|
||||||
|
|
||||||
|
While detailed documentation of the interfaces is pending, if you want to build deep integrations with Mattermost there are two drivers at the heart of the system:
|
||||||
|
|
||||||
|
### [ReactJS Javascript Driver](https://github.com/mattermost/platform/blob/master/web/react/utils/client.jsx)
|
||||||
|
|
||||||
|
[client.jsx](https://github.com/mattermost/platform/blob/master/web/react/utils/client.jsx) - This Javascript driver connects with the ReactJS components of Mattermost. The web client does the vast majority of its work by connecting to a RESTful JSON web service. There is a very small amount of processing for error checking and set up that happens on the web server.
|
||||||
|
|
||||||
|
### [Golang Driver](https://github.com/mattermost/platform/blob/master/model/client.go)
|
||||||
|
|
||||||
|
[client.go](https://github.com/mattermost/platform/blob/master/model/client.go) - This is a RESTful driver connecting with the Golang-based webservice of Mattermost and is used by unit tests.
|
||||||
@@ -14,6 +14,7 @@ To enable email, configure an SMTP email service as follows:
|
|||||||
3. Copy the `Server Name`, `Port`, `SMTP Username`, and `SMTP Password` for Step 2 below.
|
3. Copy the `Server Name`, `Port`, `SMTP Username`, and `SMTP Password` for Step 2 below.
|
||||||
4. From the `Domains` menu set up and verify a new domain, then enable `Generate DKIM Settings` for the domain.
|
4. From the `Domains` menu set up and verify a new domain, then enable `Generate DKIM Settings` for the domain.
|
||||||
5. Choose an sender address like `mattermost@example.com` and click `Send a Test Email` to verify setup is working correctly.
|
5. Choose an sender address like `mattermost@example.com` and click `Send a Test Email` to verify setup is working correctly.
|
||||||
|
|
||||||
2. **Configure SMTP settings**
|
2. **Configure SMTP settings**
|
||||||
1. Open the **System Console** by logging into an existing team and accessing "System Console" from the main menu.
|
1. Open the **System Console** by logging into an existing team and accessing "System Console" from the main menu.
|
||||||
1. Alternatively, if a team doesn't yet exist, go to `http://dockerhost:8065/` in your browser, create a team, then from the main menu click **System Console**
|
1. Alternatively, if a team doesn't yet exist, go to `http://dockerhost:8065/` in your browser, create a team, then from the main menu click **System Console**
|
||||||
@@ -29,15 +30,42 @@ To enable email, configure an SMTP email service as follows:
|
|||||||
9. **SMTP Port**: `SMTP Port` from Step 1
|
9. **SMTP Port**: `SMTP Port` from Step 1
|
||||||
10. **Connection Security**: `TLS (Recommended)`
|
10. **Connection Security**: `TLS (Recommended)`
|
||||||
11. Then click **Save**
|
11. Then click **Save**
|
||||||
|
12. Then click **Test Connection**
|
||||||
|
13. If the test failed please look in **OTHER** > **Logs** for any errors that look like `[EROR] /api/v1/admin/test_email ...`
|
||||||
|
|
||||||
|
### Known Good Sample Settings
|
||||||
|
|
||||||
|
##### Amazon SES
|
||||||
|
* Set **SMTP Username** to **AKIASKLDSKDIWEOWE**
|
||||||
|
* Set **SMTP Password** to **AdskfjAKLSDJShflsdfjkakldADkjkjdfKAJDSlkjweiqQIWEOU**
|
||||||
|
* Set **SMTP Server** to **email-smtp.us-east-1.amazonaws.com**
|
||||||
|
* Set **SMTP Port** to **465**
|
||||||
|
* Set **Connection Security** to **TLS**
|
||||||
|
|
||||||
|
##### Postfix
|
||||||
|
* Make sure Postfix is installed on the machine where Mattermost is installed
|
||||||
|
* Set **SMTP Username** to **(empty)**
|
||||||
|
* Set **SMTP Password** to **(empty)**
|
||||||
|
* Set **SMTP Server** to **localhost**
|
||||||
|
* Set **SMTP Port** to **25**
|
||||||
|
* Set **Connection Security** to **(empty)**
|
||||||
|
|
||||||
|
##### Gmail
|
||||||
|
* Information needed
|
||||||
|
|
||||||
|
##### Office 365
|
||||||
|
* Information needed
|
||||||
|
|
||||||
|
##### Hotmail
|
||||||
|
* Information needed
|
||||||
|
|
||||||
3. **Restart Mattermost**
|
|
||||||
1. Use `ps -A` to find the process ID ("pid") for service named `platform` and stop it using `kill [pid]`
|
|
||||||
2. The service should restart automatically. Run `ps -A` to verify the `platform` is running again
|
|
||||||
3. Use the reset password page (E.g. _example.com/teamname/reset_password_) to test that email is now working by entering your email and clicking **Reset my password**.
|
|
||||||
4. Note: The next time users log out, or when their session tokens expire, each will be required to verify their email address.
|
|
||||||
|
|
||||||
### Troubleshooting SMTP
|
### Troubleshooting SMTP
|
||||||
|
|
||||||
|
#### Tip 1
|
||||||
|
If you fill in **SMTP Username** and **SMTP Password** then you must set **Connection Security** to **TLS** or to **STARTTLS**
|
||||||
|
|
||||||
|
#### Tip 2
|
||||||
If you have issues with your SMTP install, from your Mattermost team site go to the main menu and open **System Console -> Logs** to look for error messages related to your setup. You can do a search for the error code to narrow down the issue. Sometimes ISPs require nuanced setups for SMTP and error codes can hint at how to make the proper adjustments.
|
If you have issues with your SMTP install, from your Mattermost team site go to the main menu and open **System Console -> Logs** to look for error messages related to your setup. You can do a search for the error code to narrow down the issue. Sometimes ISPs require nuanced setups for SMTP and error codes can hint at how to make the proper adjustments.
|
||||||
|
|
||||||
For example, if **System Console -> Logs** has an error code reading:
|
For example, if **System Console -> Logs** has an error code reading:
|
||||||
@@ -48,4 +76,19 @@ Connection unsuccessful: Failed to add to email address - 554 5.7.1 <unknown[IP-
|
|||||||
|
|
||||||
Search for `554 5.7.1 error` and `Client host rejected: Access denied`.
|
Search for `554 5.7.1 error` and `Client host rejected: Access denied`.
|
||||||
|
|
||||||
|
#### Tip 3
|
||||||
|
* Attempt to telnet to the email service to make sure the server is reachable.
|
||||||
|
* You must run the following commands from the same machine or virtual instance where `mattermost/bin/platform` is located. So if you're running Mattermost from docker you need to `docker exec -ti mattermost-dev /bin/bash`
|
||||||
|
* Telnet to the email server with `telnet mail.example.com 25`. If the command works you should see something like
|
||||||
|
```
|
||||||
|
Trying 24.121.12.143...
|
||||||
|
Connected to mail.example.com.
|
||||||
|
220 mail.example.com NO UCE ESMTP
|
||||||
|
```
|
||||||
|
* Then type something like `HELO <your mail server domain>`. If the command works you should see something like
|
||||||
|
```
|
||||||
|
250-mail.example.com NO UCE
|
||||||
|
250-STARTTLS
|
||||||
|
250-PIPELINING
|
||||||
|
250 8BITMIME
|
||||||
|
```
|
||||||
@@ -111,7 +111,7 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Respond with an auth token this can be overriden by a specific test as required
|
// Respond with an auth token this can be overriden by a specific test as required
|
||||||
sessionCookie := &http.Cookie{
|
sessionCookie := &http.Cookie{
|
||||||
Name: model.SESSION_TOKEN,
|
Name: model.SESSION_COOKIE_TOKEN,
|
||||||
Value: client.AuthToken,
|
Value: client.AuthToken,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
MaxAge: model.SESSION_TIME_WEB_IN_SECS,
|
MaxAge: model.SESSION_TIME_WEB_IN_SECS,
|
||||||
|
|||||||
@@ -16,17 +16,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
HEADER_REQUEST_ID = "X-Request-ID"
|
HEADER_REQUEST_ID = "X-Request-ID"
|
||||||
HEADER_VERSION_ID = "X-Version-ID"
|
HEADER_VERSION_ID = "X-Version-ID"
|
||||||
HEADER_ETAG_SERVER = "ETag"
|
HEADER_ETAG_SERVER = "ETag"
|
||||||
HEADER_ETAG_CLIENT = "If-None-Match"
|
HEADER_ETAG_CLIENT = "If-None-Match"
|
||||||
HEADER_FORWARDED = "X-Forwarded-For"
|
HEADER_FORWARDED = "X-Forwarded-For"
|
||||||
HEADER_REAL_IP = "X-Real-IP"
|
HEADER_REAL_IP = "X-Real-IP"
|
||||||
HEADER_FORWARDED_PROTO = "X-Forwarded-Proto"
|
HEADER_FORWARDED_PROTO = "X-Forwarded-Proto"
|
||||||
HEADER_TOKEN = "token"
|
HEADER_TOKEN = "token"
|
||||||
HEADER_BEARER = "BEARER"
|
HEADER_BEARER = "BEARER"
|
||||||
HEADER_AUTH = "Authorization"
|
HEADER_AUTH = "Authorization"
|
||||||
API_URL_SUFFIX = "/api/v1"
|
HEADER_MM_SESSION_TOKEN_INDEX = "X-MM-TokenIndex"
|
||||||
|
SESSION_TOKEN_INDEX = "session_token_index"
|
||||||
|
API_URL_SUFFIX = "/api/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Result struct {
|
type Result struct {
|
||||||
@@ -293,7 +295,7 @@ func (c *Client) login(m map[string]string) (*Result, *AppError) {
|
|||||||
} else {
|
} else {
|
||||||
c.AuthToken = r.Header.Get(HEADER_TOKEN)
|
c.AuthToken = r.Header.Get(HEADER_TOKEN)
|
||||||
c.AuthType = HEADER_BEARER
|
c.AuthType = HEADER_BEARER
|
||||||
sessionToken := getCookie(SESSION_TOKEN, r)
|
sessionToken := getCookie(SESSION_COOKIE_TOKEN, r)
|
||||||
|
|
||||||
if c.AuthToken != sessionToken.Value {
|
if c.AuthToken != sessionToken.Value {
|
||||||
NewAppError("/users/login", "Authentication tokens didn't match", "")
|
NewAppError("/users/login", "Authentication tokens didn't match", "")
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SESSION_TOKEN = "MMSID"
|
SESSION_COOKIE_TOKEN = "MMTOKEN"
|
||||||
MULTI_SESSION_TOKEN = "MMSIDMU"
|
|
||||||
SESSION_TIME_WEB_IN_DAYS = 30
|
SESSION_TIME_WEB_IN_DAYS = 30
|
||||||
SESSION_TIME_WEB_IN_SECS = 60 * 60 * 24 * SESSION_TIME_WEB_IN_DAYS
|
SESSION_TIME_WEB_IN_SECS = 60 * 60 * 24 * SESSION_TIME_WEB_IN_DAYS
|
||||||
SESSION_TIME_MOBILE_IN_DAYS = 30
|
SESSION_TIME_MOBILE_IN_DAYS = 30
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package store
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
l4g "code.google.com/p/log4go"
|
l4g "code.google.com/p/log4go"
|
||||||
|
"github.com/go-gorp/gorp"
|
||||||
"github.com/mattermost/platform/model"
|
"github.com/mattermost/platform/model"
|
||||||
"github.com/mattermost/platform/utils"
|
"github.com/mattermost/platform/utils"
|
||||||
)
|
)
|
||||||
@@ -97,49 +98,22 @@ func (s SqlChannelStore) Save(channel *model.Channel) StoreChannel {
|
|||||||
storeChannel := make(StoreChannel)
|
storeChannel := make(StoreChannel)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
result := StoreResult{}
|
var result StoreResult
|
||||||
|
if channel.Type == model.CHANNEL_DIRECT {
|
||||||
if len(channel.Id) > 0 {
|
result.Err = model.NewAppError("SqlChannelStore.Save", "Use SaveDirectChannel to create a direct channel", "")
|
||||||
result.Err = model.NewAppError("SqlChannelStore.Save",
|
|
||||||
"Must call update for exisiting channel", "id="+channel.Id)
|
|
||||||
storeChannel <- result
|
|
||||||
close(storeChannel)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
channel.PreSave()
|
|
||||||
if result.Err = channel.IsValid(); result.Err != nil {
|
|
||||||
storeChannel <- result
|
|
||||||
close(storeChannel)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if count, err := s.GetMaster().SelectInt("SELECT COUNT(0) FROM Channels WHERE TeamId = :TeamId AND DeleteAt = 0 AND (Type = 'O' OR Type = 'P')", map[string]interface{}{"TeamId": channel.TeamId}); err != nil {
|
|
||||||
result.Err = model.NewAppError("SqlChannelStore.Save", "Failed to get current channel count", "teamId="+channel.TeamId+", "+err.Error())
|
|
||||||
storeChannel <- result
|
|
||||||
close(storeChannel)
|
|
||||||
return
|
|
||||||
} else if count > 150 {
|
|
||||||
result.Err = model.NewAppError("SqlChannelStore.Save", "You've reached the limit of the number of allowed channels.", "teamId="+channel.TeamId)
|
|
||||||
storeChannel <- result
|
|
||||||
close(storeChannel)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.GetMaster().Insert(channel); err != nil {
|
|
||||||
if IsUniqueConstraintError(err.Error(), "Name", "channels_name_teamid_key") {
|
|
||||||
dupChannel := model.Channel{}
|
|
||||||
s.GetReplica().SelectOne(&dupChannel, "SELECT * FROM Channels WHERE TeamId = :TeamId AND Name = :Name AND DeleteAt > 0", map[string]interface{}{"TeamId": channel.TeamId, "Name": channel.Name})
|
|
||||||
if dupChannel.DeleteAt > 0 {
|
|
||||||
result.Err = model.NewAppError("SqlChannelStore.Update", "A channel with that URL was previously created", "id="+channel.Id+", "+err.Error())
|
|
||||||
} else {
|
|
||||||
result.Err = model.NewAppError("SqlChannelStore.Update", "A channel with that URL already exists", "id="+channel.Id+", "+err.Error())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result.Err = model.NewAppError("SqlChannelStore.Save", "We couldn't save the channel", "id="+channel.Id+", "+err.Error())
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
result.Data = channel
|
if transaction, err := s.GetMaster().Begin(); err != nil {
|
||||||
|
result.Err = model.NewAppError("SqlChannelStore.Save", "Unable to open transaction", err.Error())
|
||||||
|
} else {
|
||||||
|
result = s.saveChannelT(transaction, channel)
|
||||||
|
if result.Err != nil {
|
||||||
|
transaction.Rollback()
|
||||||
|
} else {
|
||||||
|
if err := transaction.Commit(); err != nil {
|
||||||
|
result.Err = model.NewAppError("SqlChannelStore.Save", "Unable to commit transaction", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
storeChannel <- result
|
storeChannel <- result
|
||||||
@@ -149,6 +123,100 @@ func (s SqlChannelStore) Save(channel *model.Channel) StoreChannel {
|
|||||||
return storeChannel
|
return storeChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) SaveDirectChannel(directchannel *model.Channel, member1 *model.ChannelMember, member2 *model.ChannelMember) StoreChannel {
|
||||||
|
storeChannel := make(StoreChannel)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
var result StoreResult
|
||||||
|
|
||||||
|
if directchannel.Type != model.CHANNEL_DIRECT {
|
||||||
|
result.Err = model.NewAppError("SqlChannelStore.SaveDirectChannel", "Not a direct channel attempted to be created with SaveDirectChannel", "")
|
||||||
|
} else {
|
||||||
|
if transaction, err := s.GetMaster().Begin(); err != nil {
|
||||||
|
result.Err = model.NewAppError("SqlChannelStore.SaveDirectChannel", "Unable to open transaction", err.Error())
|
||||||
|
} else {
|
||||||
|
channelResult := s.saveChannelT(transaction, directchannel)
|
||||||
|
|
||||||
|
if channelResult.Err != nil {
|
||||||
|
transaction.Rollback()
|
||||||
|
result.Err = channelResult.Err
|
||||||
|
} else {
|
||||||
|
newChannel := channelResult.Data.(*model.Channel)
|
||||||
|
// Members need new channel ID
|
||||||
|
member1.ChannelId = newChannel.Id
|
||||||
|
member2.ChannelId = newChannel.Id
|
||||||
|
|
||||||
|
member1Result := s.saveMemberT(transaction, member1, newChannel)
|
||||||
|
member2Result := s.saveMemberT(transaction, member2, newChannel)
|
||||||
|
|
||||||
|
if member1Result.Err != nil || member2Result.Err != nil {
|
||||||
|
transaction.Rollback()
|
||||||
|
details := ""
|
||||||
|
if member1Result.Err != nil {
|
||||||
|
details += "Member1Err: " + member1Result.Err.Message
|
||||||
|
}
|
||||||
|
if member2Result.Err != nil {
|
||||||
|
details += "Member2Err: " + member2Result.Err.Message
|
||||||
|
}
|
||||||
|
result.Err = model.NewAppError("SqlChannelStore.SaveDirectChannel", "Unable to add direct channel members", details)
|
||||||
|
} else {
|
||||||
|
if err := transaction.Commit(); err != nil {
|
||||||
|
result.Err = model.NewAppError("SqlChannelStore.SaveDirectChannel", "Ubable to commit transaction", err.Error())
|
||||||
|
} else {
|
||||||
|
result = channelResult
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
storeChannel <- result
|
||||||
|
close(storeChannel)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return storeChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) saveChannelT(transaction *gorp.Transaction, channel *model.Channel) StoreResult {
|
||||||
|
result := StoreResult{}
|
||||||
|
|
||||||
|
if len(channel.Id) > 0 {
|
||||||
|
result.Err = model.NewAppError("SqlChannelStore.Save", "Must call update for exisiting channel", "id="+channel.Id)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.PreSave()
|
||||||
|
if result.Err = channel.IsValid(); result.Err != nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
if count, err := transaction.SelectInt("SELECT COUNT(0) FROM Channels WHERE TeamId = :TeamId AND DeleteAt = 0 AND (Type = 'O' OR Type = 'P')", map[string]interface{}{"TeamId": channel.TeamId}); err != nil {
|
||||||
|
result.Err = model.NewAppError("SqlChannelStore.Save", "Failed to get current channel count", "teamId="+channel.TeamId+", "+err.Error())
|
||||||
|
return result
|
||||||
|
} else if count > 150 {
|
||||||
|
result.Err = model.NewAppError("SqlChannelStore.Save", "You've reached the limit of the number of allowed channels.", "teamId="+channel.TeamId)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := transaction.Insert(channel); err != nil {
|
||||||
|
if IsUniqueConstraintError(err.Error(), "Name", "channels_name_teamid_key") {
|
||||||
|
dupChannel := model.Channel{}
|
||||||
|
s.GetReplica().SelectOne(&dupChannel, "SELECT * FROM Channels WHERE TeamId = :TeamId AND Name = :Name AND DeleteAt > 0", map[string]interface{}{"TeamId": channel.TeamId, "Name": channel.Name})
|
||||||
|
if dupChannel.DeleteAt > 0 {
|
||||||
|
result.Err = model.NewAppError("SqlChannelStore.Update", "A channel with that URL was previously created", "id="+channel.Id+", "+err.Error())
|
||||||
|
} else {
|
||||||
|
result.Err = model.NewAppError("SqlChannelStore.Update", "A channel with that URL already exists", "id="+channel.Id+", "+err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.Err = model.NewAppError("SqlChannelStore.Save", "We couldn't save the channel", "id="+channel.Id+", "+err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.Data = channel
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func (s SqlChannelStore) Update(channel *model.Channel) StoreChannel {
|
func (s SqlChannelStore) Update(channel *model.Channel) StoreChannel {
|
||||||
|
|
||||||
storeChannel := make(StoreChannel)
|
storeChannel := make(StoreChannel)
|
||||||
@@ -396,31 +464,27 @@ func (s SqlChannelStore) SaveMember(member *model.ChannelMember) StoreChannel {
|
|||||||
storeChannel := make(StoreChannel)
|
storeChannel := make(StoreChannel)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
result := StoreResult{}
|
var result StoreResult
|
||||||
|
|
||||||
// Grab the channel we are saving this member to
|
// Grab the channel we are saving this member to
|
||||||
if cr := <-s.Get(member.ChannelId); cr.Err != nil {
|
if cr := <-s.Get(member.ChannelId); cr.Err != nil {
|
||||||
result.Err = cr.Err
|
result.Err = cr.Err
|
||||||
} else {
|
} else {
|
||||||
channel := cr.Data.(*model.Channel)
|
channel := cr.Data.(*model.Channel)
|
||||||
|
|
||||||
member.PreSave()
|
if transaction, err := s.GetMaster().Begin(); err != nil {
|
||||||
if result.Err = member.IsValid(); result.Err != nil {
|
result.Err = model.NewAppError("SqlChannelStore.SaveMember", "Unable to open transaction", err.Error())
|
||||||
storeChannel <- result
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.GetMaster().Insert(member); err != nil {
|
|
||||||
if IsUniqueConstraintError(err.Error(), "ChannelId", "channelmembers_pkey") {
|
|
||||||
result.Err = model.NewAppError("SqlChannelStore.SaveMember", "A channel member with that id already exists", "channel_id="+member.ChannelId+", user_id="+member.UserId+", "+err.Error())
|
|
||||||
} else {
|
|
||||||
result.Err = model.NewAppError("SqlChannelStore.SaveMember", "We couldn't save the channel member", "channel_id="+member.ChannelId+", user_id="+member.UserId+", "+err.Error())
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
result.Data = member
|
result = s.saveMemberT(transaction, member, channel)
|
||||||
// If sucessfull record members have changed in channel
|
if result.Err != nil {
|
||||||
if mu := <-s.extraUpdated(channel); mu.Err != nil {
|
transaction.Rollback()
|
||||||
result.Err = mu.Err
|
} else {
|
||||||
|
if err := transaction.Commit(); err != nil {
|
||||||
|
result.Err = model.NewAppError("SqlChannelStore.SaveMember", "Unable to commit transaction", err.Error())
|
||||||
|
}
|
||||||
|
// If sucessfull record members have changed in channel
|
||||||
|
if mu := <-s.extraUpdated(channel); mu.Err != nil {
|
||||||
|
result.Err = mu.Err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -432,6 +496,27 @@ func (s SqlChannelStore) SaveMember(member *model.ChannelMember) StoreChannel {
|
|||||||
return storeChannel
|
return storeChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s SqlChannelStore) saveMemberT(transaction *gorp.Transaction, member *model.ChannelMember, channel *model.Channel) StoreResult {
|
||||||
|
result := StoreResult{}
|
||||||
|
|
||||||
|
member.PreSave()
|
||||||
|
if result.Err = member.IsValid(); result.Err != nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := transaction.Insert(member); err != nil {
|
||||||
|
if IsUniqueConstraintError(err.Error(), "ChannelId", "channelmembers_pkey") {
|
||||||
|
result.Err = model.NewAppError("SqlChannelStore.SaveMember", "A channel member with that id already exists", "channel_id="+member.ChannelId+", user_id="+member.UserId+", "+err.Error())
|
||||||
|
} else {
|
||||||
|
result.Err = model.NewAppError("SqlChannelStore.SaveMember", "We couldn't save the channel member", "channel_id="+member.ChannelId+", user_id="+member.UserId+", "+err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.Data = member
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func (s SqlChannelStore) UpdateMember(member *model.ChannelMember) StoreChannel {
|
func (s SqlChannelStore) UpdateMember(member *model.ChannelMember) StoreChannel {
|
||||||
storeChannel := make(StoreChannel)
|
storeChannel := make(StoreChannel)
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,14 @@ func TestChannelStoreSave(t *testing.T) {
|
|||||||
t.Fatal("should be unique name")
|
t.Fatal("should be unique name")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
o1.Id = ""
|
||||||
|
o1.Name = "a" + model.NewId() + "b"
|
||||||
|
o1.Type = model.CHANNEL_DIRECT
|
||||||
|
if err := (<-store.Channel().Save(&o1)).Err; err == nil {
|
||||||
|
t.Fatal("Should not be able to save direct channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
o1.Type = model.CHANNEL_OPEN
|
||||||
for i := 0; i < 150; i++ {
|
for i := 0; i < 150; i++ {
|
||||||
o1.Id = ""
|
o1.Id = ""
|
||||||
o1.Name = "a" + model.NewId() + "b"
|
o1.Name = "a" + model.NewId() + "b"
|
||||||
@@ -48,6 +56,61 @@ func TestChannelStoreSave(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestChannelStoreSaveDirectChannel(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
teamId := model.NewId()
|
||||||
|
|
||||||
|
o1 := model.Channel{}
|
||||||
|
o1.TeamId = teamId
|
||||||
|
o1.DisplayName = "Name"
|
||||||
|
o1.Name = "a" + model.NewId() + "b"
|
||||||
|
o1.Type = model.CHANNEL_DIRECT
|
||||||
|
|
||||||
|
u1 := model.User{}
|
||||||
|
u1.TeamId = model.NewId()
|
||||||
|
u1.Email = model.NewId()
|
||||||
|
u1.Nickname = model.NewId()
|
||||||
|
Must(store.User().Save(&u1))
|
||||||
|
|
||||||
|
u2 := model.User{}
|
||||||
|
u2.TeamId = model.NewId()
|
||||||
|
u2.Email = model.NewId()
|
||||||
|
u2.Nickname = model.NewId()
|
||||||
|
Must(store.User().Save(&u2))
|
||||||
|
|
||||||
|
m1 := model.ChannelMember{}
|
||||||
|
m1.ChannelId = o1.Id
|
||||||
|
m1.UserId = u1.Id
|
||||||
|
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||||
|
|
||||||
|
m2 := model.ChannelMember{}
|
||||||
|
m2.ChannelId = o1.Id
|
||||||
|
m2.UserId = u2.Id
|
||||||
|
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||||
|
|
||||||
|
if err := (<-store.Channel().SaveDirectChannel(&o1, &m1, &m2)).Err; err != nil {
|
||||||
|
t.Fatal("couldn't save direct channel", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
members := (<-store.Channel().GetMembers(o1.Id)).Data.([]model.ChannelMember)
|
||||||
|
if len(members) != 2 {
|
||||||
|
t.Fatal("should have saved 2 members")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := (<-store.Channel().SaveDirectChannel(&o1, &m1, &m2)).Err; err == nil {
|
||||||
|
t.Fatal("shouldn't be able to update from save")
|
||||||
|
}
|
||||||
|
|
||||||
|
o1.Id = ""
|
||||||
|
o1.Name = "a" + model.NewId() + "b"
|
||||||
|
o1.Type = model.CHANNEL_OPEN
|
||||||
|
if err := (<-store.Channel().SaveDirectChannel(&o1, &m1, &m2)).Err; err == nil {
|
||||||
|
t.Fatal("Should not be able to save non-direct channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestChannelStoreUpdate(t *testing.T) {
|
func TestChannelStoreUpdate(t *testing.T) {
|
||||||
Setup()
|
Setup()
|
||||||
|
|
||||||
@@ -99,6 +162,44 @@ func TestChannelStoreGet(t *testing.T) {
|
|||||||
if err := (<-store.Channel().Get("")).Err; err == nil {
|
if err := (<-store.Channel().Get("")).Err; err == nil {
|
||||||
t.Fatal("Missing id should have failed")
|
t.Fatal("Missing id should have failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u1 := model.User{}
|
||||||
|
u1.TeamId = model.NewId()
|
||||||
|
u1.Email = model.NewId()
|
||||||
|
u1.Nickname = model.NewId()
|
||||||
|
Must(store.User().Save(&u1))
|
||||||
|
|
||||||
|
u2 := model.User{}
|
||||||
|
u2.TeamId = model.NewId()
|
||||||
|
u2.Email = model.NewId()
|
||||||
|
u2.Nickname = model.NewId()
|
||||||
|
Must(store.User().Save(&u2))
|
||||||
|
|
||||||
|
o2 := model.Channel{}
|
||||||
|
o2.TeamId = model.NewId()
|
||||||
|
o2.DisplayName = "Direct Name"
|
||||||
|
o2.Name = "a" + model.NewId() + "b"
|
||||||
|
o2.Type = model.CHANNEL_DIRECT
|
||||||
|
|
||||||
|
m1 := model.ChannelMember{}
|
||||||
|
m1.ChannelId = o2.Id
|
||||||
|
m1.UserId = u1.Id
|
||||||
|
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||||
|
|
||||||
|
m2 := model.ChannelMember{}
|
||||||
|
m2.ChannelId = o2.Id
|
||||||
|
m2.UserId = u2.Id
|
||||||
|
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||||
|
|
||||||
|
Must(store.Channel().SaveDirectChannel(&o2, &m1, &m2))
|
||||||
|
|
||||||
|
if r2 := <-store.Channel().Get(o2.Id); r2.Err != nil {
|
||||||
|
t.Fatal(r2.Err)
|
||||||
|
} else {
|
||||||
|
if r2.Data.(*model.Channel).ToJson() != o2.ToJson() {
|
||||||
|
t.Fatal("invalid returned channel")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChannelStoreDelete(t *testing.T) {
|
func TestChannelStoreDelete(t *testing.T) {
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ func (us SqlUserStore) Update(user *model.User, allowActiveUpdate bool) StoreCha
|
|||||||
oldUser := oldUserResult.(*model.User)
|
oldUser := oldUserResult.(*model.User)
|
||||||
user.CreateAt = oldUser.CreateAt
|
user.CreateAt = oldUser.CreateAt
|
||||||
user.AuthData = oldUser.AuthData
|
user.AuthData = oldUser.AuthData
|
||||||
|
user.AuthService = oldUser.AuthService
|
||||||
user.Password = oldUser.Password
|
user.Password = oldUser.Password
|
||||||
user.LastPasswordUpdate = oldUser.LastPasswordUpdate
|
user.LastPasswordUpdate = oldUser.LastPasswordUpdate
|
||||||
user.LastPictureUpdate = oldUser.LastPictureUpdate
|
user.LastPictureUpdate = oldUser.LastPictureUpdate
|
||||||
@@ -265,7 +266,7 @@ func (us SqlUserStore) UpdatePassword(userId, hashedPassword string) StoreChanne
|
|||||||
|
|
||||||
updateAt := model.GetMillis()
|
updateAt := model.GetMillis()
|
||||||
|
|
||||||
if _, err := us.GetMaster().Exec("UPDATE Users SET Password = :Password, LastPasswordUpdate = :LastPasswordUpdate, UpdateAt = :UpdateAt, FailedAttempts = 0 WHERE Id = :UserId", map[string]interface{}{"Password": hashedPassword, "LastPasswordUpdate": updateAt, "UpdateAt": updateAt, "UserId": userId}); err != nil {
|
if _, err := us.GetMaster().Exec("UPDATE Users SET Password = :Password, LastPasswordUpdate = :LastPasswordUpdate, UpdateAt = :UpdateAt, FailedAttempts = 0 WHERE Id = :UserId AND AuthData = ''", map[string]interface{}{"Password": hashedPassword, "LastPasswordUpdate": updateAt, "UpdateAt": updateAt, "UserId": userId}); err != nil {
|
||||||
result.Err = model.NewAppError("SqlUserStore.UpdatePassword", "We couldn't update the user password", "id="+userId+", "+err.Error())
|
result.Err = model.NewAppError("SqlUserStore.UpdatePassword", "We couldn't update the user password", "id="+userId+", "+err.Error())
|
||||||
} else {
|
} else {
|
||||||
result.Data = userId
|
result.Data = userId
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ type TeamStore interface {
|
|||||||
|
|
||||||
type ChannelStore interface {
|
type ChannelStore interface {
|
||||||
Save(channel *model.Channel) StoreChannel
|
Save(channel *model.Channel) StoreChannel
|
||||||
|
SaveDirectChannel(channel *model.Channel, member1 *model.ChannelMember, member2 *model.ChannelMember) StoreChannel
|
||||||
Update(channel *model.Channel) StoreChannel
|
Update(channel *model.Channel) StoreChannel
|
||||||
Get(id string) StoreChannel
|
Get(id string) StoreChannel
|
||||||
Delete(channelId string, time int64) StoreChannel
|
Delete(channelId string, time int64) StoreChannel
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const (
|
|||||||
var Cfg *model.Config = &model.Config{}
|
var Cfg *model.Config = &model.Config{}
|
||||||
var CfgLastModified int64 = 0
|
var CfgLastModified int64 = 0
|
||||||
var CfgFileName string = ""
|
var CfgFileName string = ""
|
||||||
var ClientProperties map[string]string = map[string]string{}
|
var ClientCfg map[string]string = map[string]string{}
|
||||||
var SanitizeOptions map[string]bool = map[string]bool{}
|
var SanitizeOptions map[string]bool = map[string]bool{}
|
||||||
|
|
||||||
func FindConfigFile(fileName string) string {
|
func FindConfigFile(fileName string) string {
|
||||||
@@ -161,7 +161,7 @@ func LoadConfig(fileName string) {
|
|||||||
|
|
||||||
Cfg = &config
|
Cfg = &config
|
||||||
SanitizeOptions = getSanitizeOptions(Cfg)
|
SanitizeOptions = getSanitizeOptions(Cfg)
|
||||||
ClientProperties = getClientProperties(Cfg)
|
ClientCfg = getClientConfig(Cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSanitizeOptions(c *model.Config) map[string]bool {
|
func getSanitizeOptions(c *model.Config) map[string]bool {
|
||||||
@@ -172,7 +172,7 @@ func getSanitizeOptions(c *model.Config) map[string]bool {
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
func getClientProperties(c *model.Config) map[string]string {
|
func getClientConfig(c *model.Config) map[string]string {
|
||||||
props := make(map[string]string)
|
props := make(map[string]string)
|
||||||
|
|
||||||
props["Version"] = model.CurrentVersion
|
props["Version"] = model.CurrentVersion
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default class AboutBuildModal extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const config = global.window.config;
|
const config = global.window.mm_config;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
var AdminNavbarDropdown = require('./admin_navbar_dropdown.jsx');
|
var AdminNavbarDropdown = require('./admin_navbar_dropdown.jsx');
|
||||||
var UserStore = require('../../stores/user_store.jsx');
|
var UserStore = require('../../stores/user_store.jsx');
|
||||||
|
var Utils = require('../../utils/utils.jsx');
|
||||||
|
|
||||||
export default class SidebarHeader extends React.Component {
|
export default class SidebarHeader extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -36,7 +37,7 @@ export default class SidebarHeader extends React.Component {
|
|||||||
profilePicture = (
|
profilePicture = (
|
||||||
<img
|
<img
|
||||||
className='user__picture'
|
className='user__picture'
|
||||||
src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at}
|
src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at + '&' + Utils.getSessionIndex()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ export default class UserItem extends React.Component {
|
|||||||
<div className='row member-div'>
|
<div className='row member-div'>
|
||||||
<img
|
<img
|
||||||
className='post-profile-img pull-left'
|
className='post-profile-img pull-left'
|
||||||
src={`/api/v1/users/${user.id}/image?time=${user.update_at}`}
|
src={`/api/v1/users/${user.id}/image?time=${user.update_at}&${Utils.getSessionIndex()}`}
|
||||||
height='36'
|
height='36'
|
||||||
width='36'
|
width='36'
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export default class ChannelLoader extends React.Component {
|
|||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
/* Initial aysnc loads */
|
/* Initial aysnc loads */
|
||||||
AsyncClient.getMe();
|
|
||||||
AsyncClient.getPosts(ChannelStore.getCurrentId());
|
AsyncClient.getPosts(ChannelStore.getCurrentId());
|
||||||
AsyncClient.getChannels(true, true);
|
AsyncClient.getChannels(true, true);
|
||||||
AsyncClient.getChannelExtraInfo(true);
|
AsyncClient.getChannelExtraInfo(true);
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export default class CreatePost extends React.Component {
|
|||||||
submitting: false,
|
submitting: false,
|
||||||
initialText: draft.messageText,
|
initialText: draft.messageText,
|
||||||
windowWidth: Utils.windowWidth(),
|
windowWidth: Utils.windowWidth(),
|
||||||
windowHeigth: Utils.windowHeight()
|
windowHeight: Utils.windowHeight()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
handleResize() {
|
handleResize() {
|
||||||
@@ -71,7 +71,7 @@ export default class CreatePost extends React.Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevState.windowWidth !== this.state.windowWidth || prevState.windowHeight !== this.state.windowHeigth) {
|
if (prevState.windowWidth !== this.state.windowWidth || prevState.windowHeight !== this.state.windowHeight) {
|
||||||
this.resizePostHolder();
|
this.resizePostHolder();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -208,7 +208,7 @@ export default class CreatePost extends React.Component {
|
|||||||
PostStore.storeCurrentDraft(draft);
|
PostStore.storeCurrentDraft(draft);
|
||||||
}
|
}
|
||||||
resizePostHolder() {
|
resizePostHolder() {
|
||||||
const height = this.state.windowHeigth - $(ReactDOM.findDOMNode(this.refs.topDiv)).height() - 50;
|
const height = this.state.windowHeight - $(ReactDOM.findDOMNode(this.refs.topDiv)).height() - 50;
|
||||||
$('.post-list-holder-by-time').css('height', `${height}px`);
|
$('.post-list-holder-by-time').css('height', `${height}px`);
|
||||||
if (this.state.windowWidth > 960) {
|
if (this.state.windowWidth > 960) {
|
||||||
$('#post_textbox').focus();
|
$('#post_textbox').focus();
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ export default class EmailVerify extends React.Component {
|
|||||||
var resend = '';
|
var resend = '';
|
||||||
var resendConfirm = '';
|
var resendConfirm = '';
|
||||||
if (this.props.isVerified === 'true') {
|
if (this.props.isVerified === 'true') {
|
||||||
title = global.window.config.SiteName + ' Email Verified';
|
title = global.window.mm_config.SiteName + ' Email Verified';
|
||||||
body = <p>Your email has been verified! Click <a href={this.props.teamURL + '?email=' + this.props.userEmail}>here</a> to log in.</p>;
|
body = <p>Your email has been verified! Click <a href={this.props.teamURL + '?email=' + this.props.userEmail}>here</a> to log in.</p>;
|
||||||
} else {
|
} else {
|
||||||
title = global.window.config.SiteName + ': You are almost done';
|
title = global.window.mm_config.SiteName + ': You are almost done';
|
||||||
body = <p>Please verify your email address. Check your inbox for an email.</p>;
|
body = <p>Please verify your email address. Check your inbox for an email.</p>;
|
||||||
resend = (
|
resend = (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export default class FileAttachment extends React.Component {
|
|||||||
|
|
||||||
if (type === 'image') {
|
if (type === 'image') {
|
||||||
var self = this; // Need this reference since we use the given "this"
|
var self = this; // Need this reference since we use the given "this"
|
||||||
$('<img/>').attr('src', fileInfo.path + '_thumb.jpg').load(function loadWrapper(path, name) {
|
$('<img/>').attr('src', fileInfo.path + '_thumb.jpg?' + utils.getSessionIndex()).load(function loadWrapper(path, name) {
|
||||||
return function loader() {
|
return function loader() {
|
||||||
$(this).remove();
|
$(this).remove();
|
||||||
if (name in self.refs) {
|
if (name in self.refs) {
|
||||||
@@ -147,7 +147,7 @@ export default class FileAttachment extends React.Component {
|
|||||||
var re3 = new RegExp('\\)', 'g');
|
var re3 = new RegExp('\\)', 'g');
|
||||||
var url = fileUrl.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29');
|
var url = fileUrl.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29');
|
||||||
|
|
||||||
$(imgDiv).css('background-image', 'url(' + url + '_thumb.jpg)');
|
$(imgDiv).css('background-image', 'url(' + url + '_thumb.jpg?' + utils.getSessionIndex() + ')');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
removeBackgroundImage(name) {
|
removeBackgroundImage(name) {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default class FilePreview extends React.Component {
|
|||||||
if (filename.indexOf('/api/v1/files/get') !== -1) {
|
if (filename.indexOf('/api/v1/files/get') !== -1) {
|
||||||
filename = filename.split('/api/v1/files/get')[1];
|
filename = filename.split('/api/v1/files/get')[1];
|
||||||
}
|
}
|
||||||
filename = Utils.getWindowLocationOrigin() + '/api/v1/files/get' + filename;
|
filename = Utils.getWindowLocationOrigin() + '/api/v1/files/get' + filename + '?' + Utils.getSessionIndex();
|
||||||
|
|
||||||
if (type === 'image') {
|
if (type === 'image') {
|
||||||
previews.push(
|
previews.push(
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default class InviteMemberModal extends React.Component {
|
|||||||
emailErrors: {},
|
emailErrors: {},
|
||||||
firstNameErrors: {},
|
firstNameErrors: {},
|
||||||
lastNameErrors: {},
|
lastNameErrors: {},
|
||||||
emailEnabled: global.window.config.SendEmailNotifications === 'true'
|
emailEnabled: global.window.mm_config.SendEmailNotifications === 'true'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export default class Login extends React.Component {
|
|||||||
}
|
}
|
||||||
handleSubmit(e) {
|
handleSubmit(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let state = {};
|
var state = {};
|
||||||
|
|
||||||
const name = this.props.teamName;
|
const name = this.props.teamName;
|
||||||
if (!name) {
|
if (!name) {
|
||||||
@@ -49,8 +49,7 @@ export default class Login extends React.Component {
|
|||||||
this.setState(state);
|
this.setState(state);
|
||||||
|
|
||||||
Client.loginByEmail(name, email, password,
|
Client.loginByEmail(name, email, password,
|
||||||
function loggedIn(data) {
|
() => {
|
||||||
UserStore.setCurrentUser(data);
|
|
||||||
UserStore.setLastEmail(email);
|
UserStore.setLastEmail(email);
|
||||||
|
|
||||||
const redirect = Utils.getUrlParameter('redirect');
|
const redirect = Utils.getUrlParameter('redirect');
|
||||||
@@ -60,7 +59,7 @@ export default class Login extends React.Component {
|
|||||||
window.location.href = '/' + name + '/channels/town-square';
|
window.location.href = '/' + name + '/channels/town-square';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function loginFailed(err) {
|
(err) => {
|
||||||
if (err.message === 'Login failed because email address has not been verified') {
|
if (err.message === 'Login failed because email address has not been verified') {
|
||||||
window.location.href = '/verify_email?teamname=' + encodeURIComponent(name) + '&email=' + encodeURIComponent(email);
|
window.location.href = '/verify_email?teamname=' + encodeURIComponent(name) + '&email=' + encodeURIComponent(email);
|
||||||
return;
|
return;
|
||||||
@@ -68,7 +67,7 @@ export default class Login extends React.Component {
|
|||||||
state.serverError = err.message;
|
state.serverError = err.message;
|
||||||
this.valid = false;
|
this.valid = false;
|
||||||
this.setState(state);
|
this.setState(state);
|
||||||
}.bind(this)
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
@@ -95,7 +94,7 @@ export default class Login extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let loginMessage = [];
|
let loginMessage = [];
|
||||||
if (global.window.config.EnableSignUpWithGitLab === 'true') {
|
if (global.window.mm_config.EnableSignUpWithGitLab === 'true') {
|
||||||
loginMessage.push(
|
loginMessage.push(
|
||||||
<a
|
<a
|
||||||
className='btn btn-custom-login gitlab'
|
className='btn btn-custom-login gitlab'
|
||||||
@@ -124,7 +123,7 @@ export default class Login extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let emailSignup;
|
let emailSignup;
|
||||||
if (global.window.config.EnableSignUpWithEmail === 'true') {
|
if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
|
||||||
emailSignup = (
|
emailSignup = (
|
||||||
<div>
|
<div>
|
||||||
<div className={'form-group' + errorClass}>
|
<div className={'form-group' + errorClass}>
|
||||||
@@ -186,7 +185,7 @@ export default class Login extends React.Component {
|
|||||||
<div className='signup-team__container'>
|
<div className='signup-team__container'>
|
||||||
<h5 className='margin--less'>Sign in to:</h5>
|
<h5 className='margin--less'>Sign in to:</h5>
|
||||||
<h2 className='signup-team__name'>{teamDisplayName}</h2>
|
<h2 className='signup-team__name'>{teamDisplayName}</h2>
|
||||||
<h2 className='signup-team__subdomain'>on {global.window.config.SiteName}</h2>
|
<h2 className='signup-team__subdomain'>on {global.window.mm_config.SiteName}</h2>
|
||||||
<form onSubmit={this.handleSubmit}>
|
<form onSubmit={this.handleSubmit}>
|
||||||
{verifiedBox}
|
{verifiedBox}
|
||||||
<div className={'form-group' + errorClass}>
|
<div className={'form-group' + errorClass}>
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ export default class MemberListItem extends React.Component {
|
|||||||
<div className='row member-div'>
|
<div className='row member-div'>
|
||||||
<img
|
<img
|
||||||
className='post-profile-img pull-left'
|
className='post-profile-img pull-left'
|
||||||
src={'/api/v1/users/' + member.id + '/image?time=' + timestamp}
|
src={'/api/v1/users/' + member.id + '/image?time=' + timestamp + '&' + Utils.getSessionIndex()}
|
||||||
height='36'
|
height='36'
|
||||||
width='36'
|
width='36'
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ export default class MemberListTeamItem extends React.Component {
|
|||||||
<div className='row member-div'>
|
<div className='row member-div'>
|
||||||
<img
|
<img
|
||||||
className='post-profile-img pull-left'
|
className='post-profile-img pull-left'
|
||||||
src={`/api/v1/users/${user.id}/image?time=${timestamp}`}
|
src={`/api/v1/users/${user.id}/image?time=${timestamp}&${Utils.getSessionIndex()}`}
|
||||||
height='36'
|
height='36'
|
||||||
width='36'
|
width='36'
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
var UserStore = require('../stores/user_store.jsx');
|
var UserStore = require('../stores/user_store.jsx');
|
||||||
|
const Utils = require('../utils/utils.jsx');
|
||||||
|
|
||||||
export default class Mention extends React.Component {
|
export default class Mention extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -25,7 +26,7 @@ export default class Mention extends React.Component {
|
|||||||
<span>
|
<span>
|
||||||
<img
|
<img
|
||||||
className='mention-img'
|
className='mention-img'
|
||||||
src={'/api/v1/users/' + this.props.id + '/image?time=' + timestamp}
|
src={'/api/v1/users/' + this.props.id + '/image?time=' + timestamp + '&' + Utils.getSessionIndex()}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export default class MoreDirectChannels extends React.Component {
|
|||||||
|
|
||||||
getUsersFromStore() {
|
getUsersFromStore() {
|
||||||
const currentId = UserStore.getCurrentId();
|
const currentId = UserStore.getCurrentId();
|
||||||
const profiles = UserStore.getProfiles();
|
const profiles = UserStore.getActiveOnlyProfiles();
|
||||||
const users = [];
|
const users = [];
|
||||||
|
|
||||||
for (const id in profiles) {
|
for (const id in profiles) {
|
||||||
@@ -178,7 +178,7 @@ export default class MoreDirectChannels extends React.Component {
|
|||||||
className='profile-img pull-left'
|
className='profile-img pull-left'
|
||||||
width='38'
|
width='38'
|
||||||
height='38'
|
height='38'
|
||||||
src={`/api/v1/users/${user.id}/image?time=${user.update_at}`}
|
src={`/api/v1/users/${user.id}/image?time=${user.update_at}&${Utils.getSessionIndex()}`}
|
||||||
/>
|
/>
|
||||||
<div className='more-name'>
|
<div className='more-name'>
|
||||||
{user.username}
|
{user.username}
|
||||||
@@ -209,12 +209,14 @@ export default class MoreDirectChannels extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let users = this.state.users;
|
let users = this.state.users;
|
||||||
if (this.state.filter !== '') {
|
if (this.state.filter) {
|
||||||
|
const filter = this.state.filter.toLowerCase();
|
||||||
|
|
||||||
users = users.filter((user) => {
|
users = users.filter((user) => {
|
||||||
return user.username.indexOf(this.state.filter) !== -1 ||
|
return user.username.toLowerCase().indexOf(filter) !== -1 ||
|
||||||
user.first_name.indexOf(this.state.filter) !== -1 ||
|
user.first_name.toLowerCase().indexOf(filter) !== -1 ||
|
||||||
user.last_name.indexOf(this.state.filter) !== -1 ||
|
user.last_name.toLowerCase().indexOf(filter) !== -1 ||
|
||||||
user.nickname.indexOf(this.state.filter) !== -1;
|
user.nickname.toLowerCase().indexOf(filter) !== -1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ export default class NavbarDropdown extends React.Component {
|
|||||||
sysAdminLink = (
|
sysAdminLink = (
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href='/admin_console'
|
href={'/admin_console?' + Utils.getSessionIndex()}
|
||||||
>
|
>
|
||||||
{'System Console'}
|
{'System Console'}
|
||||||
</a>
|
</a>
|
||||||
@@ -178,7 +178,7 @@ export default class NavbarDropdown extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (global.window.config.EnableTeamCreation === 'true') {
|
if (global.window.mm_config.EnableTeamCreation === 'true') {
|
||||||
teams.push(
|
teams.push(
|
||||||
<li key='newTeam_li'>
|
<li key='newTeam_li'>
|
||||||
<a
|
<a
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export default class PasswordResetForm extends React.Component {
|
|||||||
<div className='signup-team__container'>
|
<div className='signup-team__container'>
|
||||||
<h3>Password Reset</h3>
|
<h3>Password Reset</h3>
|
||||||
<form onSubmit={this.handlePasswordReset}>
|
<form onSubmit={this.handlePasswordReset}>
|
||||||
<p>{'Enter a new password for your ' + this.props.teamDisplayName + ' ' + global.window.config.SiteName + ' account.'}</p>
|
<p>{'Enter a new password for your ' + this.props.teamDisplayName + ' ' + global.window.mm_config.SiteName + ' account.'}</p>
|
||||||
<div className={formClass}>
|
<div className={formClass}>
|
||||||
<input
|
<input
|
||||||
type='password'
|
type='password'
|
||||||
|
|||||||
@@ -158,8 +158,8 @@ export default class Post extends React.Component {
|
|||||||
|
|
||||||
var profilePic = null;
|
var profilePic = null;
|
||||||
if (!this.props.hideProfilePic) {
|
if (!this.props.hideProfilePic) {
|
||||||
let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp;
|
let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp + '&' + utils.getSessionIndex();
|
||||||
if (post.props && post.props.from_webhook && global.window.config.EnablePostIconOverride === 'true') {
|
if (post.props && post.props.from_webhook && global.window.mm_config.EnablePostIconOverride === 'true') {
|
||||||
if (post.props.override_icon_url) {
|
if (post.props.override_icon_url) {
|
||||||
src = post.props.override_icon_url;
|
src = post.props.override_icon_url;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,12 +119,12 @@ export default class PostBody extends React.Component {
|
|||||||
this.setState({youtubeTitle: metadata.title});
|
this.setState({youtubeTitle: metadata.title});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (global.window.config.GoogleDeveloperKey && !this.receivedYoutubeData) {
|
if (global.window.mm_config.GoogleDeveloperKey && !this.receivedYoutubeData) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
async: true,
|
async: true,
|
||||||
url: 'https://www.googleapis.com/youtube/v3/videos',
|
url: 'https://www.googleapis.com/youtube/v3/videos',
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
data: {part: 'snippet', id: youtubeId, key: global.window.config.GoogleDeveloperKey},
|
data: {part: 'snippet', id: youtubeId, key: global.window.mm_config.GoogleDeveloperKey},
|
||||||
success: success.bind(this)
|
success: success.bind(this)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export default class PostHeader extends React.Component {
|
|||||||
let botIndicator;
|
let botIndicator;
|
||||||
|
|
||||||
if (post.props && post.props.from_webhook) {
|
if (post.props && post.props.from_webhook) {
|
||||||
if (post.props.override_username && global.window.config.EnablePostUsernameOverride === 'true') {
|
if (post.props.override_username && global.window.mm_config.EnablePostUsernameOverride === 'true') {
|
||||||
userProfile = (
|
userProfile = (
|
||||||
<UserProfile
|
<UserProfile
|
||||||
userId={post.user_id}
|
userId={post.user_id}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const UserStore = require('../stores/user_store.jsx');
|
|||||||
const SocketStore = require('../stores/socket_store.jsx');
|
const SocketStore = require('../stores/socket_store.jsx');
|
||||||
const PreferenceStore = require('../stores/preference_store.jsx');
|
const PreferenceStore = require('../stores/preference_store.jsx');
|
||||||
|
|
||||||
const utils = require('../utils/utils.jsx');
|
const Utils = require('../utils/utils.jsx');
|
||||||
const Client = require('../utils/client.jsx');
|
const Client = require('../utils/client.jsx');
|
||||||
const Constants = require('../utils/constants.jsx');
|
const Constants = require('../utils/constants.jsx');
|
||||||
const ActionTypes = Constants.ActionTypes;
|
const ActionTypes = Constants.ActionTypes;
|
||||||
@@ -40,11 +40,13 @@ export default class PostList extends React.Component {
|
|||||||
this.loadFirstPosts = this.loadFirstPosts.bind(this);
|
this.loadFirstPosts = this.loadFirstPosts.bind(this);
|
||||||
this.activate = this.activate.bind(this);
|
this.activate = this.activate.bind(this);
|
||||||
this.deactivate = this.deactivate.bind(this);
|
this.deactivate = this.deactivate.bind(this);
|
||||||
this.resize = this.resize.bind(this);
|
this.handleResize = this.handleResize.bind(this);
|
||||||
|
this.resizePostList = this.resizePostList.bind(this);
|
||||||
|
|
||||||
const state = this.getStateFromStores(props.channelId);
|
const state = this.getStateFromStores(props.channelId);
|
||||||
state.numToDisplay = Constants.POST_CHUNK_SIZE;
|
state.numToDisplay = Constants.POST_CHUNK_SIZE;
|
||||||
state.isFirstLoadComplete = false;
|
state.isFirstLoadComplete = false;
|
||||||
|
state.windowHeight = Utils.windowHeight();
|
||||||
|
|
||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
@@ -115,12 +117,7 @@ export default class PostList extends React.Component {
|
|||||||
|
|
||||||
const postHolder = $(ReactDOM.findDOMNode(this.refs.postlist));
|
const postHolder = $(ReactDOM.findDOMNode(this.refs.postlist));
|
||||||
|
|
||||||
$(window).resize(() => {
|
window.addEventListener('resize', this.handleResize);
|
||||||
this.resize();
|
|
||||||
if (!this.scrolled) {
|
|
||||||
this.scrollToBottom();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
postHolder.on('scroll', () => {
|
postHolder.on('scroll', () => {
|
||||||
const position = postHolder.scrollTop() + postHolder.height() + 14;
|
const position = postHolder.scrollTop() + postHolder.height() + 14;
|
||||||
@@ -154,7 +151,7 @@ export default class PostList extends React.Component {
|
|||||||
this.loadFirstPosts(this.props.channelId);
|
this.loadFirstPosts(this.props.channelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.resize();
|
this.resizePostList();
|
||||||
this.onChange();
|
this.onChange();
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
}
|
}
|
||||||
@@ -164,7 +161,9 @@ export default class PostList extends React.Component {
|
|||||||
SocketStore.removeChangeListener(this.onSocketChange);
|
SocketStore.removeChangeListener(this.onSocketChange);
|
||||||
PreferenceStore.removeChangeListener(this.onTimeChange);
|
PreferenceStore.removeChangeListener(this.onTimeChange);
|
||||||
$('body').off('click.userpopover');
|
$('body').off('click.userpopover');
|
||||||
$(window).off('resize');
|
|
||||||
|
window.removeEventListener('resize', this.handleResize);
|
||||||
|
|
||||||
var postHolder = $(ReactDOM.findDOMNode(this.refs.postlist));
|
var postHolder = $(ReactDOM.findDOMNode(this.refs.postlist));
|
||||||
postHolder.off('scroll');
|
postHolder.off('scroll');
|
||||||
}
|
}
|
||||||
@@ -173,6 +172,13 @@ export default class PostList extends React.Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prevState.windowHeight !== this.state.windowHeight) {
|
||||||
|
this.resizePostList();
|
||||||
|
if (!this.scrolled) {
|
||||||
|
this.scrollToBottom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$('.post-list__content div .post').removeClass('post--last');
|
$('.post-list__content div .post').removeClass('post--last');
|
||||||
$('.post-list__content div:last-child .post').addClass('post--last');
|
$('.post-list__content div:last-child .post').addClass('post--last');
|
||||||
|
|
||||||
@@ -202,7 +208,7 @@ export default class PostList extends React.Component {
|
|||||||
// it's by the user and not a comment
|
// it's by the user and not a comment
|
||||||
} else if (isNewPost &&
|
} else if (isNewPost &&
|
||||||
userId === firstPost.user_id &&
|
userId === firstPost.user_id &&
|
||||||
!utils.isComment(firstPost)) {
|
!Utils.isComment(firstPost)) {
|
||||||
this.scrollToBottom(true);
|
this.scrollToBottom(true);
|
||||||
|
|
||||||
// the user clicked 'load more messages'
|
// the user clicked 'load more messages'
|
||||||
@@ -231,10 +237,15 @@ export default class PostList extends React.Component {
|
|||||||
this.deactivate();
|
this.deactivate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resize() {
|
handleResize() {
|
||||||
|
this.setState({
|
||||||
|
windowHeight: Utils.windowHeight()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
resizePostList() {
|
||||||
const postHolder = $(ReactDOM.findDOMNode(this.refs.postlist));
|
const postHolder = $(ReactDOM.findDOMNode(this.refs.postlist));
|
||||||
if ($('#create_post').length > 0) {
|
if ($('#create_post').length > 0) {
|
||||||
const height = $(window).height() - $('#create_post').height() - $('#error_bar').outerHeight() - 50;
|
const height = this.state.windowHeight - $('#create_post').height() - $('#error_bar').outerHeight() - 50;
|
||||||
postHolder.css('height', height + 'px');
|
postHolder.css('height', height + 'px');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,7 +291,7 @@ export default class PostList extends React.Component {
|
|||||||
onChange() {
|
onChange() {
|
||||||
var newState = this.getStateFromStores(this.props.channelId);
|
var newState = this.getStateFromStores(this.props.channelId);
|
||||||
|
|
||||||
if (!utils.areStatesEqual(newState.postList, this.state.postList)) {
|
if (!Utils.areStatesEqual(newState.postList, this.state.postList)) {
|
||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -310,7 +321,7 @@ export default class PostList extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
createDMIntroMessage(channel) {
|
createDMIntroMessage(channel) {
|
||||||
var teammate = utils.getDirectTeammate(channel.id);
|
var teammate = Utils.getDirectTeammate(channel.id);
|
||||||
|
|
||||||
if (teammate) {
|
if (teammate) {
|
||||||
var teammateName = teammate.username;
|
var teammateName = teammate.username;
|
||||||
@@ -323,7 +334,7 @@ export default class PostList extends React.Component {
|
|||||||
<div className='post-profile-img__container channel-intro-img'>
|
<div className='post-profile-img__container channel-intro-img'>
|
||||||
<img
|
<img
|
||||||
className='post-profile-img'
|
className='post-profile-img'
|
||||||
src={'/api/v1/users/' + teammate.id + '/image?time=' + teammate.update_at}
|
src={'/api/v1/users/' + teammate.id + '/image?time=' + teammate.update_at + '&' + Utils.getSessionIndex()}
|
||||||
height='50'
|
height='50'
|
||||||
width='50'
|
width='50'
|
||||||
/>
|
/>
|
||||||
@@ -370,13 +381,13 @@ export default class PostList extends React.Component {
|
|||||||
createDefaultIntroMessage(channel) {
|
createDefaultIntroMessage(channel) {
|
||||||
return (
|
return (
|
||||||
<div className='channel-intro'>
|
<div className='channel-intro'>
|
||||||
<h4 className='channel-intro__title'>Beginning of {channel.display_name}</h4>
|
<h4 className='channel-intro__title'>{'Beginning of ' + channel.display_name}</h4>
|
||||||
<p className='channel-intro__content'>
|
<p className='channel-intro__content'>
|
||||||
Welcome to {channel.display_name}!
|
{'Welcome to ' + channel.display_name + '!'}
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.
|
{'This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.'}
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
To create a new channel or join an existing one, go to the Left Sidebar under “Channels” and click “More…”.
|
{'To create a new channel or join an existing one, go to the Left Sidebar under “Channels” and click “More…”.'}
|
||||||
<br/>
|
<br/>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -385,7 +396,7 @@ export default class PostList extends React.Component {
|
|||||||
createOffTopicIntroMessage(channel) {
|
createOffTopicIntroMessage(channel) {
|
||||||
return (
|
return (
|
||||||
<div className='channel-intro'>
|
<div className='channel-intro'>
|
||||||
<h4 className='channel-intro__title'>Beginning of {channel.display_name}</h4>
|
<h4 className='channel-intro__title'>{'Beginning of ' + channel.display_name}</h4>
|
||||||
<p className='channel-intro__content'>
|
<p className='channel-intro__content'>
|
||||||
{'This is the start of ' + channel.display_name + ', a channel for non-work-related conversations.'}
|
{'This is the start of ' + channel.display_name + ', a channel for non-work-related conversations.'}
|
||||||
<br/>
|
<br/>
|
||||||
@@ -399,7 +410,7 @@ export default class PostList extends React.Component {
|
|||||||
data-title={channel.display_name}
|
data-title={channel.display_name}
|
||||||
data-channelid={channel.id}
|
data-channelid={channel.id}
|
||||||
>
|
>
|
||||||
<i className='fa fa-pencil'></i>Set a description
|
<i className='fa fa-pencil'></i>{'Set a description'}
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
className='intro-links'
|
className='intro-links'
|
||||||
@@ -407,7 +418,7 @@ export default class PostList extends React.Component {
|
|||||||
data-toggle='modal'
|
data-toggle='modal'
|
||||||
data-target='#channel_invite'
|
data-target='#channel_invite'
|
||||||
>
|
>
|
||||||
<i className='fa fa-user-plus'></i>Invite others to this channel
|
<i className='fa fa-user-plus'></i>{'Invite others to this channel'}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -422,7 +433,7 @@ export default class PostList extends React.Component {
|
|||||||
|
|
||||||
var members = ChannelStore.getExtraInfo(channel.id).members;
|
var members = ChannelStore.getExtraInfo(channel.id).members;
|
||||||
for (var i = 0; i < members.length; i++) {
|
for (var i = 0; i < members.length; i++) {
|
||||||
if (utils.isAdmin(members[i].roles)) {
|
if (Utils.isAdmin(members[i].roles)) {
|
||||||
return members[i].username;
|
return members[i].username;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -443,14 +454,14 @@ export default class PostList extends React.Component {
|
|||||||
|
|
||||||
var createMessage;
|
var createMessage;
|
||||||
if (creatorName === '') {
|
if (creatorName === '') {
|
||||||
createMessage = 'This is the start of the ' + uiName + ' ' + uiType + ', created on ' + utils.displayDate(channel.create_at) + '.';
|
createMessage = 'This is the start of the ' + uiName + ' ' + uiType + ', created on ' + Utils.displayDate(channel.create_at) + '.';
|
||||||
} else {
|
} else {
|
||||||
createMessage = (<span>This is the start of the <strong>{uiName}</strong> {uiType}, created by <strong>{creatorName}</strong> on <strong>{utils.displayDate(channel.create_at)}</strong></span>);
|
createMessage = (<span>This is the start of the <strong>{uiName}</strong> {uiType}, created by <strong>{creatorName}</strong> on <strong>{Utils.displayDate(channel.create_at)}</strong></span>);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='channel-intro'>
|
<div className='channel-intro'>
|
||||||
<h4 className='channel-intro__title'>Beginning of {uiName}</h4>
|
<h4 className='channel-intro__title'>{'Beginning of ' + uiName}</h4>
|
||||||
<p className='channel-intro__content'>
|
<p className='channel-intro__content'>
|
||||||
{createMessage}
|
{createMessage}
|
||||||
{memberMessage}
|
{memberMessage}
|
||||||
@@ -465,7 +476,7 @@ export default class PostList extends React.Component {
|
|||||||
data-title={channel.display_name}
|
data-title={channel.display_name}
|
||||||
data-channelid={channel.id}
|
data-channelid={channel.id}
|
||||||
>
|
>
|
||||||
<i className='fa fa-pencil'></i>Set a description
|
<i className='fa fa-pencil'></i>{'Set a description'}
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
className='intro-links'
|
className='intro-links'
|
||||||
@@ -473,7 +484,7 @@ export default class PostList extends React.Component {
|
|||||||
data-toggle='modal'
|
data-toggle='modal'
|
||||||
data-target='#channel_invite'
|
data-target='#channel_invite'
|
||||||
>
|
>
|
||||||
<i className='fa fa-user-plus'></i>Invite others to this {uiType}
|
<i className='fa fa-user-plus'></i>{'Invite others to this ' + uiType}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -507,7 +518,7 @@ export default class PostList extends React.Component {
|
|||||||
if (prevPost) {
|
if (prevPost) {
|
||||||
sameUser = prevPost.user_id === post.user_id && post.create_at - prevPost.create_at <= 1000 * 60 * 5;
|
sameUser = prevPost.user_id === post.user_id && post.create_at - prevPost.create_at <= 1000 * 60 * 5;
|
||||||
|
|
||||||
sameRoot = utils.isComment(post) && (prevPost.id === post.root_id || prevPost.root_id === post.root_id);
|
sameRoot = Utils.isComment(post) && (prevPost.id === post.root_id || prevPost.root_id === post.root_id);
|
||||||
|
|
||||||
// hide the profile pic if:
|
// hide the profile pic if:
|
||||||
// the previous post was made by the same user as the current post,
|
// the previous post was made by the same user as the current post,
|
||||||
@@ -516,8 +527,8 @@ export default class PostList extends React.Component {
|
|||||||
// the current post is not from a webhook
|
// the current post is not from a webhook
|
||||||
// and the previous post is not from a webhook
|
// and the previous post is not from a webhook
|
||||||
if ((prevPost.user_id === post.user_id) &&
|
if ((prevPost.user_id === post.user_id) &&
|
||||||
!utils.isComment(prevPost) &&
|
!Utils.isComment(prevPost) &&
|
||||||
!utils.isComment(post) &&
|
!Utils.isComment(post) &&
|
||||||
(!post.props || !post.props.from_webhook) &&
|
(!post.props || !post.props.from_webhook) &&
|
||||||
(!prevPost.props || !prevPost.props.from_webhook)) {
|
(!prevPost.props || !prevPost.props.from_webhook)) {
|
||||||
hideProfilePic = true;
|
hideProfilePic = true;
|
||||||
@@ -526,7 +537,7 @@ export default class PostList extends React.Component {
|
|||||||
|
|
||||||
// check if it's the last comment in a consecutive string of comments on the same post
|
// check if it's the last comment in a consecutive string of comments on the same post
|
||||||
// it is the last comment if it is last post in the channel or the next post has a different root post
|
// it is the last comment if it is last post in the channel or the next post has a different root post
|
||||||
var isLastComment = utils.isComment(post) && (i === 0 || posts[order[i - 1]].root_id !== post.root_id);
|
var isLastComment = Utils.isComment(post) && (i === 0 || posts[order[i - 1]].root_id !== post.root_id);
|
||||||
|
|
||||||
var postCtl = (
|
var postCtl = (
|
||||||
<Post
|
<Post
|
||||||
@@ -542,7 +553,7 @@ export default class PostList extends React.Component {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
let currentPostDay = utils.getDateForUnixTicks(post.create_at);
|
const currentPostDay = Utils.getDateForUnixTicks(post.create_at);
|
||||||
if (currentPostDay.toDateString() !== previousPostDay.toDateString()) {
|
if (currentPostDay.toDateString() !== previousPostDay.toDateString()) {
|
||||||
postCtls.push(
|
postCtls.push(
|
||||||
<div
|
<div
|
||||||
@@ -558,9 +569,9 @@ export default class PostList extends React.Component {
|
|||||||
if (post.user_id !== userId && post.create_at > lastViewed && !renderedLastViewed) {
|
if (post.user_id !== userId && post.create_at > lastViewed && !renderedLastViewed) {
|
||||||
renderedLastViewed = true;
|
renderedLastViewed = true;
|
||||||
|
|
||||||
// Temporary fix to solve ie10/11 rendering issue
|
// Temporary fix to solve ie11 rendering issue
|
||||||
let newSeparatorId = '';
|
let newSeparatorId = '';
|
||||||
if (!utils.isBrowserIE()) {
|
if (!Utils.isBrowserIE()) {
|
||||||
newSeparatorId = 'new_message_' + this.props.channelId;
|
newSeparatorId = 'new_message_' + this.props.channelId;
|
||||||
}
|
}
|
||||||
postCtls.push(
|
postCtls.push(
|
||||||
@@ -572,7 +583,7 @@ export default class PostList extends React.Component {
|
|||||||
<hr
|
<hr
|
||||||
className='separator__hr'
|
className='separator__hr'
|
||||||
/>
|
/>
|
||||||
<div className='separator__text'>New Messages</div>
|
<div className='separator__text'>{'New Messages'}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -638,7 +649,7 @@ export default class PostList extends React.Component {
|
|||||||
order = this.state.postList.order;
|
order = this.state.postList.order;
|
||||||
}
|
}
|
||||||
|
|
||||||
var moreMessages = <p className='beginning-messages-text'>Beginning of Channel</p>;
|
var moreMessages = <p className='beginning-messages-text'>{'Beginning of Channel'}</p>;
|
||||||
if (channel != null) {
|
if (channel != null) {
|
||||||
if (order.length >= this.state.numToDisplay) {
|
if (order.length >= this.state.numToDisplay) {
|
||||||
moreMessages = (
|
moreMessages = (
|
||||||
@@ -648,7 +659,7 @@ export default class PostList extends React.Component {
|
|||||||
href='#'
|
href='#'
|
||||||
onClick={this.loadMorePosts}
|
onClick={this.loadMorePosts}
|
||||||
>
|
>
|
||||||
Load more messages
|
{'Load more messages'}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ export default class RhsComment extends React.Component {
|
|||||||
<div className='post-profile-img__container'>
|
<div className='post-profile-img__container'>
|
||||||
<img
|
<img
|
||||||
className='post-profile-img'
|
className='post-profile-img'
|
||||||
src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp}
|
src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp + '&' + Utils.getSessionIndex()}
|
||||||
height='36'
|
height='36'
|
||||||
width='36'
|
width='36'
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export default class RhsRootPost extends React.Component {
|
|||||||
let botIndicator;
|
let botIndicator;
|
||||||
|
|
||||||
if (post.props && post.props.from_webhook) {
|
if (post.props && post.props.from_webhook) {
|
||||||
if (post.props.override_username && global.window.config.EnablePostUsernameOverride === 'true') {
|
if (post.props.override_username && global.window.mm_config.EnablePostUsernameOverride === 'true') {
|
||||||
userProfile = (
|
userProfile = (
|
||||||
<UserProfile
|
<UserProfile
|
||||||
userId={post.user_id}
|
userId={post.user_id}
|
||||||
@@ -134,8 +134,8 @@ export default class RhsRootPost extends React.Component {
|
|||||||
botIndicator = <li className='post-header-col post-header__name bot-indicator'>{'BOT'}</li>;
|
botIndicator = <li className='post-header-col post-header__name bot-indicator'>{'BOT'}</li>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp;
|
let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp + '&' + utils.getSessionIndex();
|
||||||
if (post.props && post.props.from_webhook && global.window.config.EnablePostIconOverride === 'true') {
|
if (post.props && post.props.from_webhook && global.window.mm_config.EnablePostIconOverride === 'true') {
|
||||||
if (post.props.override_icon_url) {
|
if (post.props.override_icon_url) {
|
||||||
src = post.props.override_icon_url;
|
src = post.props.override_icon_url;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
|||||||
var utils = require('../utils/utils.jsx');
|
var utils = require('../utils/utils.jsx');
|
||||||
var Constants = require('../utils/constants.jsx');
|
var Constants = require('../utils/constants.jsx');
|
||||||
var ActionTypes = Constants.ActionTypes;
|
var ActionTypes = Constants.ActionTypes;
|
||||||
|
var Tooltip = ReactBootstrap.Tooltip;
|
||||||
|
|
||||||
export default class SearchBar extends React.Component {
|
export default class SearchBar extends React.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -16,10 +17,14 @@ export default class SearchBar extends React.Component {
|
|||||||
|
|
||||||
this.onListenerChange = this.onListenerChange.bind(this);
|
this.onListenerChange = this.onListenerChange.bind(this);
|
||||||
this.handleUserInput = this.handleUserInput.bind(this);
|
this.handleUserInput = this.handleUserInput.bind(this);
|
||||||
|
this.handleUserFocus = this.handleUserFocus.bind(this);
|
||||||
|
this.handleUserBlur = this.handleUserBlur.bind(this);
|
||||||
this.performSearch = this.performSearch.bind(this);
|
this.performSearch = this.performSearch.bind(this);
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
|
|
||||||
this.state = this.getSearchTermStateFromStores();
|
const state = this.getSearchTermStateFromStores();
|
||||||
|
state.focused = false;
|
||||||
|
this.state = state;
|
||||||
}
|
}
|
||||||
getSearchTermStateFromStores() {
|
getSearchTermStateFromStores() {
|
||||||
var term = PostStore.getSearchTerm() || '';
|
var term = PostStore.getSearchTerm() || '';
|
||||||
@@ -78,9 +83,14 @@ export default class SearchBar extends React.Component {
|
|||||||
handleMouseInput(e) {
|
handleMouseInput(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
handleUserBlur() {
|
||||||
|
this.setState({focused: false});
|
||||||
|
}
|
||||||
handleUserFocus(e) {
|
handleUserFocus(e) {
|
||||||
e.target.select();
|
e.target.select();
|
||||||
$('.search-bar__container').addClass('focused');
|
$('.search-bar__container').addClass('focused');
|
||||||
|
|
||||||
|
this.setState({focused: true});
|
||||||
}
|
}
|
||||||
performSearch(terms, isMentionSearch) {
|
performSearch(terms, isMentionSearch) {
|
||||||
if (terms.length) {
|
if (terms.length) {
|
||||||
@@ -115,6 +125,12 @@ export default class SearchBar extends React.Component {
|
|||||||
if (this.state.isSearching) {
|
if (this.state.isSearching) {
|
||||||
isSearching = <span className={'glyphicon glyphicon-refresh glyphicon-refresh-animate'}></span>;
|
isSearching = <span className={'glyphicon glyphicon-refresh glyphicon-refresh-animate'}></span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let helpClass = 'search-help-popover';
|
||||||
|
if (!this.state.searchTerm && this.state.focused) {
|
||||||
|
helpClass += ' visible';
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
@@ -142,10 +158,25 @@ export default class SearchBar extends React.Component {
|
|||||||
placeholder='Search'
|
placeholder='Search'
|
||||||
value={this.state.searchTerm}
|
value={this.state.searchTerm}
|
||||||
onFocus={this.handleUserFocus}
|
onFocus={this.handleUserFocus}
|
||||||
|
onBlur={this.handleUserBlur}
|
||||||
onChange={this.handleUserInput}
|
onChange={this.handleUserInput}
|
||||||
onMouseUp={this.handleMouseInput}
|
onMouseUp={this.handleMouseInput}
|
||||||
/>
|
/>
|
||||||
{isSearching}
|
{isSearching}
|
||||||
|
<Tooltip
|
||||||
|
placement='bottom'
|
||||||
|
className={helpClass}
|
||||||
|
>
|
||||||
|
<h4>{'Search Options'}</h4>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span>{'Use '}</span><b>{'"quotation marks"'}</b><span>{' to search for phrases'}</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span>{'Use '}</span><b>{'from:'}</b><span>{' to find posts from specific users and '}</span><b>{'in:'}</b><span>{' to find posts in specific channels'}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Tooltip>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export default class SearchResultsItem extends React.Component {
|
|||||||
<div className='post-profile-img__container'>
|
<div className='post-profile-img__container'>
|
||||||
<img
|
<img
|
||||||
className='post-profile-img'
|
className='post-profile-img'
|
||||||
src={'/api/v1/users/' + this.props.post.user_id + '/image?time=' + timestamp}
|
src={'/api/v1/users/' + this.props.post.user_id + '/image?time=' + timestamp + '&' + utils.getSessionIndex()}
|
||||||
height='36'
|
height='36'
|
||||||
width='36'
|
width='36'
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export default class SettingPicture extends React.Component {
|
|||||||
>Save</a>
|
>Save</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
var helpText = 'Upload a profile picture in either JPG or PNG format, at least ' + global.window.config.ProfileWidth + 'px in width and ' + global.window.config.ProfileHeight + 'px height.';
|
var helpText = 'Upload a profile picture in either JPG or PNG format, at least ' + global.window.mm_config.ProfileWidth + 'px in width and ' + global.window.mm_config.ProfileHeight + 'px height.';
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -183,8 +183,8 @@ export default class Sidebar extends React.Component {
|
|||||||
const channel = ChannelStore.getCurrent();
|
const channel = ChannelStore.getCurrent();
|
||||||
if (channel) {
|
if (channel) {
|
||||||
let currentSiteName = '';
|
let currentSiteName = '';
|
||||||
if (global.window.config.SiteName != null) {
|
if (global.window.mm_config.SiteName != null) {
|
||||||
currentSiteName = global.window.config.SiteName;
|
currentSiteName = global.window.mm_config.SiteName;
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentChannelName = channel.display_name;
|
let currentChannelName = channel.display_name;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
var NavbarDropdown = require('./navbar_dropdown.jsx');
|
var NavbarDropdown = require('./navbar_dropdown.jsx');
|
||||||
var UserStore = require('../stores/user_store.jsx');
|
var UserStore = require('../stores/user_store.jsx');
|
||||||
|
const Utils = require('../utils/utils.jsx');
|
||||||
|
|
||||||
export default class SidebarHeader extends React.Component {
|
export default class SidebarHeader extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -32,7 +33,7 @@ export default class SidebarHeader extends React.Component {
|
|||||||
profilePicture = (
|
profilePicture = (
|
||||||
<img
|
<img
|
||||||
className='user__picture'
|
className='user__picture'
|
||||||
src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at}
|
src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at + '&' + Utils.getSessionIndex()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -61,7 +62,7 @@ export default class SidebarHeader extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SidebarHeader.defaultProps = {
|
SidebarHeader.defaultProps = {
|
||||||
teamDisplayName: global.window.config.SiteName,
|
teamDisplayName: global.window.mm_config.SiteName,
|
||||||
teamType: ''
|
teamType: ''
|
||||||
};
|
};
|
||||||
SidebarHeader.propTypes = {
|
SidebarHeader.propTypes = {
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export default class SidebarRightMenu extends React.Component {
|
|||||||
consoleLink = (
|
consoleLink = (
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href='/admin_console'
|
href={'/admin_console?' + utils.getSessionIndex()}
|
||||||
>
|
>
|
||||||
<i className='glyphicon glyphicon-wrench'></i>System Console</a>
|
<i className='glyphicon glyphicon-wrench'></i>System Console</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -92,8 +92,8 @@ export default class SidebarRightMenu extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var siteName = '';
|
var siteName = '';
|
||||||
if (global.window.config.SiteName != null) {
|
if (global.window.mm_config.SiteName != null) {
|
||||||
siteName = global.window.config.SiteName;
|
siteName = global.window.mm_config.SiteName;
|
||||||
}
|
}
|
||||||
var teamDisplayName = siteName;
|
var teamDisplayName = siteName;
|
||||||
if (this.props.teamDisplayName) {
|
if (this.props.teamDisplayName) {
|
||||||
|
|||||||
@@ -14,19 +14,19 @@ export default class TeamSignUp extends React.Component {
|
|||||||
|
|
||||||
var count = 0;
|
var count = 0;
|
||||||
|
|
||||||
if (global.window.config.EnableSignUpWithEmail === 'true') {
|
if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
|
||||||
count = count + 1;
|
count = count + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (global.window.config.EnableSignUpWithGitLab === 'true') {
|
if (global.window.mm_config.EnableSignUpWithGitLab === 'true') {
|
||||||
count = count + 1;
|
count = count + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count > 1) {
|
if (count > 1) {
|
||||||
this.state = {page: 'choose'};
|
this.state = {page: 'choose'};
|
||||||
} else if (global.window.config.EnableSignUpWithEmail === 'true') {
|
} else if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
|
||||||
this.state = {page: 'email'};
|
this.state = {page: 'email'};
|
||||||
} else if (global.window.config.EnableSignUpWithGitLab === 'true') {
|
} else if (global.window.mm_config.EnableSignUpWithGitLab === 'true') {
|
||||||
this.state = {page: 'gitlab'};
|
this.state = {page: 'gitlab'};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,30 +82,29 @@ export default class SignupUserComplete extends React.Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
client.createUser(user, this.props.data, this.props.hash,
|
client.createUser(user, this.props.data, this.props.hash,
|
||||||
function createUserSuccess() {
|
() => {
|
||||||
client.track('signup', 'signup_user_02_complete');
|
client.track('signup', 'signup_user_02_complete');
|
||||||
|
|
||||||
client.loginByEmail(this.props.teamName, user.email, user.password,
|
client.loginByEmail(this.props.teamName, user.email, user.password,
|
||||||
function emailLoginSuccess(data) {
|
() => {
|
||||||
UserStore.setLastEmail(user.email);
|
UserStore.setLastEmail(user.email);
|
||||||
UserStore.setCurrentUser(data);
|
|
||||||
if (this.props.hash > 0) {
|
if (this.props.hash > 0) {
|
||||||
BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: 'finished'}));
|
BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: 'finished'}));
|
||||||
}
|
}
|
||||||
window.location.href = '/' + this.props.teamName + '/channels/town-square';
|
window.location.href = '/' + this.props.teamName + '/channels/town-square';
|
||||||
}.bind(this),
|
},
|
||||||
function emailLoginFailure(err) {
|
(err) => {
|
||||||
if (err.message === 'Login failed because email address has not been verified') {
|
if (err.message === 'Login failed because email address has not been verified') {
|
||||||
window.location.href = '/verify_email?email=' + encodeURIComponent(user.email) + '&teamname=' + encodeURIComponent(this.props.teamName);
|
window.location.href = '/verify_email?email=' + encodeURIComponent(user.email) + '&teamname=' + encodeURIComponent(this.props.teamName);
|
||||||
} else {
|
} else {
|
||||||
this.setState({serverError: err.message});
|
this.setState({serverError: err.message});
|
||||||
}
|
}
|
||||||
}.bind(this)
|
}
|
||||||
);
|
);
|
||||||
}.bind(this),
|
},
|
||||||
function createUserFailure(err) {
|
(err) => {
|
||||||
this.setState({serverError: err.message});
|
this.setState({serverError: err.message});
|
||||||
}.bind(this)
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
@@ -149,7 +148,7 @@ export default class SignupUserComplete extends React.Component {
|
|||||||
// set up the email entry and hide it if an email was provided
|
// set up the email entry and hide it if an email was provided
|
||||||
var yourEmailIs = '';
|
var yourEmailIs = '';
|
||||||
if (this.state.user.email) {
|
if (this.state.user.email) {
|
||||||
yourEmailIs = <span>Your email address is <strong>{this.state.user.email}</strong>. You'll use this address to sign in to {global.window.config.SiteName}.</span>;
|
yourEmailIs = <span>Your email address is <strong>{this.state.user.email}</strong>. You'll use this address to sign in to {global.window.mm_config.SiteName}.</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
var emailContainerStyle = 'margin--extra';
|
var emailContainerStyle = 'margin--extra';
|
||||||
@@ -177,7 +176,7 @@ export default class SignupUserComplete extends React.Component {
|
|||||||
);
|
);
|
||||||
|
|
||||||
var signupMessage = [];
|
var signupMessage = [];
|
||||||
if (global.window.config.EnableSignUpWithGitLab === 'true') {
|
if (global.window.mm_config.EnableSignUpWithGitLab === 'true') {
|
||||||
signupMessage.push(
|
signupMessage.push(
|
||||||
<a
|
<a
|
||||||
className='btn btn-custom-login gitlab'
|
className='btn btn-custom-login gitlab'
|
||||||
@@ -190,7 +189,7 @@ export default class SignupUserComplete extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var emailSignup;
|
var emailSignup;
|
||||||
if (global.window.config.EnableSignUpWithEmail === 'true') {
|
if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
|
||||||
emailSignup = (
|
emailSignup = (
|
||||||
<div>
|
<div>
|
||||||
<div className='inner__content'>
|
<div className='inner__content'>
|
||||||
@@ -259,7 +258,7 @@ export default class SignupUserComplete extends React.Component {
|
|||||||
/>
|
/>
|
||||||
<h5 className='margin--less'>Welcome to:</h5>
|
<h5 className='margin--less'>Welcome to:</h5>
|
||||||
<h2 className='signup-team__name'>{this.props.teamDisplayName}</h2>
|
<h2 className='signup-team__name'>{this.props.teamDisplayName}</h2>
|
||||||
<h2 className='signup-team__subdomain'>on {global.window.config.SiteName}</h2>
|
<h2 className='signup-team__subdomain'>on {global.window.mm_config.SiteName}</h2>
|
||||||
<h4 className='color--light'>Let's create your account</h4>
|
<h4 className='color--light'>Let's create your account</h4>
|
||||||
{signupMessage}
|
{signupMessage}
|
||||||
{emailSignup}
|
{emailSignup}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default class ChooseAuthPage extends React.Component {
|
|||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
var buttons = [];
|
var buttons = [];
|
||||||
if (global.window.config.EnableSignUpWithGitLab === 'true') {
|
if (global.window.mm_config.EnableSignUpWithGitLab === 'true') {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<a
|
<a
|
||||||
className='btn btn-custom-login gitlab btn-full'
|
className='btn btn-custom-login gitlab btn-full'
|
||||||
@@ -26,7 +26,7 @@ export default class ChooseAuthPage extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (global.window.config.EnableSignUpWithEmail === 'true') {
|
if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<a
|
<a
|
||||||
className='btn btn-custom-login email btn-full'
|
className='btn btn-custom-login email btn-full'
|
||||||
|
|||||||
@@ -36,15 +36,14 @@ export default class TeamSignupPasswordPage extends React.Component {
|
|||||||
delete teamSignup.wizard;
|
delete teamSignup.wizard;
|
||||||
|
|
||||||
Client.createTeamFromSignup(teamSignup,
|
Client.createTeamFromSignup(teamSignup,
|
||||||
function success() {
|
() => {
|
||||||
Client.track('signup', 'signup_team_08_complete');
|
Client.track('signup', 'signup_team_08_complete');
|
||||||
|
|
||||||
var props = this.props;
|
var props = this.props;
|
||||||
|
|
||||||
Client.loginByEmail(teamSignup.team.name, teamSignup.team.email, teamSignup.user.password,
|
Client.loginByEmail(teamSignup.team.name, teamSignup.team.email, teamSignup.user.password,
|
||||||
function loginSuccess(data) {
|
() => {
|
||||||
UserStore.setLastEmail(teamSignup.team.email);
|
UserStore.setLastEmail(teamSignup.team.email);
|
||||||
UserStore.setCurrentUser(data);
|
|
||||||
if (this.props.hash > 0) {
|
if (this.props.hash > 0) {
|
||||||
BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: 'finished'}));
|
BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: 'finished'}));
|
||||||
}
|
}
|
||||||
@@ -54,21 +53,21 @@ export default class TeamSignupPasswordPage extends React.Component {
|
|||||||
props.updateParent(props.state, true);
|
props.updateParent(props.state, true);
|
||||||
|
|
||||||
window.location.href = '/' + teamSignup.team.name + '/channels/town-square';
|
window.location.href = '/' + teamSignup.team.name + '/channels/town-square';
|
||||||
}.bind(this),
|
},
|
||||||
function loginFail(err) {
|
(err) => {
|
||||||
if (err.message === 'Login failed because email address has not been verified') {
|
if (err.message === 'Login failed because email address has not been verified') {
|
||||||
window.location.href = '/verify_email?email=' + encodeURIComponent(teamSignup.team.email) + '&teamname=' + encodeURIComponent(teamSignup.team.name);
|
window.location.href = '/verify_email?email=' + encodeURIComponent(teamSignup.team.email) + '&teamname=' + encodeURIComponent(teamSignup.team.name);
|
||||||
} else {
|
} else {
|
||||||
this.setState({serverError: err.message});
|
this.setState({serverError: err.message});
|
||||||
$('#finish-button').button('reset');
|
$('#finish-button').button('reset');
|
||||||
}
|
}
|
||||||
}.bind(this)
|
}
|
||||||
);
|
);
|
||||||
}.bind(this),
|
},
|
||||||
function error(err) {
|
(err) => {
|
||||||
this.setState({serverError: err.message});
|
this.setState({serverError: err.message});
|
||||||
$('#finish-button').button('reset');
|
$('#finish-button').button('reset');
|
||||||
}.bind(this)
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
@@ -129,7 +128,7 @@ export default class TeamSignupPasswordPage extends React.Component {
|
|||||||
Finish
|
Finish
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p>By proceeding to create your account and use {global.window.config.SiteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {global.window.config.SiteName}.</p>
|
<p>By proceeding to create your account and use {global.window.mm_config.SiteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {global.window.mm_config.SiteName}.</p>
|
||||||
<div className='margin--extra'>
|
<div className='margin--extra'>
|
||||||
<a
|
<a
|
||||||
href='#'
|
href='#'
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default class TeamSignupSendInvitesPage extends React.Component {
|
|||||||
this.submitSkip = this.submitSkip.bind(this);
|
this.submitSkip = this.submitSkip.bind(this);
|
||||||
this.keySubmit = this.keySubmit.bind(this);
|
this.keySubmit = this.keySubmit.bind(this);
|
||||||
this.state = {
|
this.state = {
|
||||||
emailEnabled: global.window.config.SendEmailNotifications === 'true'
|
emailEnabled: global.window.mm_config.SendEmailNotifications === 'true'
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!this.state.emailEnabled) {
|
if (!this.state.emailEnabled) {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export default class TeamSignupUrlPage extends React.Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (global.window.config.RestrictTeamNames === 'true') {
|
if (global.window.mm_config.RestrictTeamNames === 'true') {
|
||||||
for (let index = 0; index < Constants.RESERVED_TEAM_NAMES.length; index++) {
|
for (let index = 0; index < Constants.RESERVED_TEAM_NAMES.length; index++) {
|
||||||
if (cleanedName.indexOf(Constants.RESERVED_TEAM_NAMES[index]) === 0) {
|
if (cleanedName.indexOf(Constants.RESERVED_TEAM_NAMES[index]) === 0) {
|
||||||
this.setState({nameError: 'URL is taken or contains a reserved word'});
|
this.setState({nameError: 'URL is taken or contains a reserved word'});
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default class TeamSignupUsernamePage extends React.Component {
|
|||||||
}
|
}
|
||||||
submitBack(e) {
|
submitBack(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (global.window.config.SendEmailNotifications === 'true') {
|
if (global.window.mm_config.SendEmailNotifications === 'true') {
|
||||||
this.props.state.wizard = 'send_invites';
|
this.props.state.wizard = 'send_invites';
|
||||||
} else {
|
} else {
|
||||||
this.props.state.wizard = 'team_url';
|
this.props.state.wizard = 'team_url';
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export default class TeamSignupWelcomePage extends React.Component {
|
|||||||
src='/static/images/logo.png'
|
src='/static/images/logo.png'
|
||||||
/>
|
/>
|
||||||
<h3 className='sub-heading'>Welcome to:</h3>
|
<h3 className='sub-heading'>Welcome to:</h3>
|
||||||
<h1 className='margin--top-none'>{global.window.config.SiteName}</h1>
|
<h1 className='margin--top-none'>{global.window.mm_config.SiteName}</h1>
|
||||||
</p>
|
</p>
|
||||||
<p className='margin--less'>Let's set up your new team</p>
|
<p className='margin--less'>Let's set up your new team</p>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -67,13 +67,14 @@ export default class UserProfile extends React.Component {
|
|||||||
dataContent.push(
|
dataContent.push(
|
||||||
<img
|
<img
|
||||||
className='user-popover__image'
|
className='user-popover__image'
|
||||||
src={'/api/v1/users/' + this.state.profile.id + '/image?time=' + this.state.profile.update_at}
|
src={'/api/v1/users/' + this.state.profile.id + '/image?time=' + this.state.profile.update_at + '&' + Utils.getSessionIndex()}
|
||||||
height='128'
|
height='128'
|
||||||
width='128'
|
width='128'
|
||||||
key='user-popover-image'
|
key='user-popover-image'
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
if (!global.window.config.ShowEmailAddress === 'true') {
|
|
||||||
|
if (!global.window.mm_config.ShowEmailAddress === 'true') {
|
||||||
dataContent.push(
|
dataContent.push(
|
||||||
<div
|
<div
|
||||||
className='text-nowrap'
|
className='text-nowrap'
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export default class UserSettingsGeneralTab extends React.Component {
|
|||||||
() => {
|
() => {
|
||||||
this.updateSection('');
|
this.updateSection('');
|
||||||
AsyncClient.getMe();
|
AsyncClient.getMe();
|
||||||
const verificationEnabled = global.window.config.SendEmailNotifications === 'true' && global.window.config.RequireEmailVerification === 'true' && emailUpdated;
|
const verificationEnabled = global.window.mm_config.SendEmailNotifications === 'true' && global.window.mm_config.RequireEmailVerification === 'true' && emailUpdated;
|
||||||
|
|
||||||
if (verificationEnabled) {
|
if (verificationEnabled) {
|
||||||
ErrorStore.storeLastError({message: 'Check your email at ' + user.email + ' to verify the address.'});
|
ErrorStore.storeLastError({message: 'Check your email at ' + user.email + ' to verify the address.'});
|
||||||
@@ -451,8 +451,8 @@ export default class UserSettingsGeneralTab extends React.Component {
|
|||||||
}
|
}
|
||||||
var emailSection;
|
var emailSection;
|
||||||
if (this.props.activeSection === 'email') {
|
if (this.props.activeSection === 'email') {
|
||||||
const emailEnabled = global.window.config.SendEmailNotifications === 'true';
|
const emailEnabled = global.window.mm_config.SendEmailNotifications === 'true';
|
||||||
const emailVerificationEnabled = global.window.config.RequireEmailVerification === 'true';
|
const emailVerificationEnabled = global.window.mm_config.RequireEmailVerification === 'true';
|
||||||
let helpText = 'Email is used for notifications, and requires verification if changed.';
|
let helpText = 'Email is used for notifications, and requires verification if changed.';
|
||||||
|
|
||||||
if (!emailEnabled) {
|
if (!emailEnabled) {
|
||||||
@@ -542,7 +542,7 @@ export default class UserSettingsGeneralTab extends React.Component {
|
|||||||
<SettingPicture
|
<SettingPicture
|
||||||
title='Profile Picture'
|
title='Profile Picture'
|
||||||
submit={this.submitPicture}
|
submit={this.submitPicture}
|
||||||
src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update}
|
src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update + '&' + utils.getSessionIndex()}
|
||||||
server_error={serverError}
|
server_error={serverError}
|
||||||
client_error={clientError}
|
client_error={clientError}
|
||||||
updateSection={function clearSection(e) {
|
updateSection={function clearSection(e) {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
|
|||||||
let outgoingHooksSection;
|
let outgoingHooksSection;
|
||||||
var inputs = [];
|
var inputs = [];
|
||||||
|
|
||||||
if (global.window.config.EnableIncomingWebhooks === 'true') {
|
if (global.window.mm_config.EnableIncomingWebhooks === 'true') {
|
||||||
if (this.props.activeSection === 'incoming-hooks') {
|
if (this.props.activeSection === 'incoming-hooks') {
|
||||||
inputs.push(
|
inputs.push(
|
||||||
<ManageIncomingHooks />
|
<ManageIncomingHooks />
|
||||||
@@ -65,7 +65,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (global.window.config.EnableOutgoingWebhooks === 'true') {
|
if (global.window.mm_config.EnableOutgoingWebhooks === 'true') {
|
||||||
if (this.props.activeSection === 'outgoing-hooks') {
|
if (this.props.activeSection === 'outgoing-hooks') {
|
||||||
inputs.push(
|
inputs.push(
|
||||||
<ManageOutgoingHooks />
|
<ManageOutgoingHooks />
|
||||||
|
|||||||
@@ -35,10 +35,11 @@ export default class UserSettingsModal extends React.Component {
|
|||||||
tabs.push({name: 'security', uiName: 'Security', icon: 'glyphicon glyphicon-lock'});
|
tabs.push({name: 'security', uiName: 'Security', icon: 'glyphicon glyphicon-lock'});
|
||||||
tabs.push({name: 'notifications', uiName: 'Notifications', icon: 'glyphicon glyphicon-exclamation-sign'});
|
tabs.push({name: 'notifications', uiName: 'Notifications', icon: 'glyphicon glyphicon-exclamation-sign'});
|
||||||
tabs.push({name: 'appearance', uiName: 'Appearance', icon: 'glyphicon glyphicon-wrench'});
|
tabs.push({name: 'appearance', uiName: 'Appearance', icon: 'glyphicon glyphicon-wrench'});
|
||||||
if (global.window.config.EnableOAuthServiceProvider === 'true') {
|
if (global.window.mm_config.EnableOAuthServiceProvider === 'true') {
|
||||||
tabs.push({name: 'developer', uiName: 'Developer', icon: 'glyphicon glyphicon-th'});
|
tabs.push({name: 'developer', uiName: 'Developer', icon: 'glyphicon glyphicon-th'});
|
||||||
}
|
}
|
||||||
if (global.window.config.EnableIncomingWebhooks === 'true' || global.window.config.EnableOutgoingWebhooks === 'true') {
|
|
||||||
|
if (global.window.mm_config.EnableIncomingWebhooks === 'true' || global.window.mm_config.EnableOutgoingWebhooks === 'true') {
|
||||||
tabs.push({name: 'integrations', uiName: 'Integrations', icon: 'glyphicon glyphicon-transfer'});
|
tabs.push({name: 'integrations', uiName: 'Integrations', icon: 'glyphicon glyphicon-transfer'});
|
||||||
}
|
}
|
||||||
tabs.push({name: 'display', uiName: 'Display', icon: 'glyphicon glyphicon-eye-open'});
|
tabs.push({name: 'display', uiName: 'Display', icon: 'glyphicon glyphicon-eye-open'});
|
||||||
|
|||||||
@@ -413,7 +413,7 @@ export default class NotificationsTab extends React.Component {
|
|||||||
</label>
|
</label>
|
||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
<div><br/>{'Email notifications are sent for mentions and direct messages after you’ve been offline for more than 60 seconds or away from ' + global.window.config.SiteName + ' for more than 5 minutes.'}</div>
|
<div><br/>{'Email notifications are sent for mentions and direct messages after you’ve been offline for more than 60 seconds or away from ' + global.window.mm_config.SiteName + ' for more than 5 minutes.'}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ export default class ViewImageModal extends React.Component {
|
|||||||
}
|
}
|
||||||
fileInfo.path = Utils.getWindowLocationOrigin() + '/api/v1/files/get' + fileInfo.path;
|
fileInfo.path = Utils.getWindowLocationOrigin() + '/api/v1/files/get' + fileInfo.path;
|
||||||
|
|
||||||
return fileInfo.path + '_preview.jpg';
|
return fileInfo.path + '_preview.jpg' + '?' + Utils.getSessionIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
// only images have proper previews, so just use a placeholder icon for non-images
|
// only images have proper previews, so just use a placeholder icon for non-images
|
||||||
@@ -306,7 +306,7 @@ export default class ViewImageModal extends React.Component {
|
|||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
>
|
>
|
||||||
<source src={Utils.getWindowLocationOrigin() + '/api/v1/files/get' + filename} />
|
<source src={Utils.getWindowLocationOrigin() + '/api/v1/files/get' + filename + '?' + Utils.getSessionIndex()} />
|
||||||
</video>
|
</video>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export default class ViewImagePopoverBar extends React.Component {
|
|||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
var publicLink = '';
|
var publicLink = '';
|
||||||
if (global.window.config.EnablePublicLink === 'true') {
|
if (global.window.mm_config.EnablePublicLink === 'true') {
|
||||||
publicLink = (
|
publicLink = (
|
||||||
<div>
|
<div>
|
||||||
<a
|
<a
|
||||||
|
|||||||
@@ -35,26 +35,18 @@ var RemovedFromChannelModal = require('../components/removed_from_channel_modal.
|
|||||||
var FileUploadOverlay = require('../components/file_upload_overlay.jsx');
|
var FileUploadOverlay = require('../components/file_upload_overlay.jsx');
|
||||||
var RegisterAppModal = require('../components/register_app_modal.jsx');
|
var RegisterAppModal = require('../components/register_app_modal.jsx');
|
||||||
var ImportThemeModal = require('../components/user_settings/import_theme_modal.jsx');
|
var ImportThemeModal = require('../components/user_settings/import_theme_modal.jsx');
|
||||||
var TeamStore = require('../stores/team_store.jsx');
|
|
||||||
|
|
||||||
var AsyncClient = require('../utils/async_client.jsx');
|
var AsyncClient = require('../utils/async_client.jsx');
|
||||||
var Constants = require('../utils/constants.jsx');
|
var Constants = require('../utils/constants.jsx');
|
||||||
var ActionTypes = Constants.ActionTypes;
|
var ActionTypes = Constants.ActionTypes;
|
||||||
|
|
||||||
function setupChannelPage(props) {
|
function setupChannelPage(props) {
|
||||||
TeamStore.setCurrentId(props.TeamId);
|
|
||||||
|
|
||||||
AppDispatcher.handleViewAction({
|
AppDispatcher.handleViewAction({
|
||||||
type: ActionTypes.CLICK_CHANNEL,
|
type: ActionTypes.CLICK_CHANNEL,
|
||||||
name: props.ChannelName,
|
name: props.ChannelName,
|
||||||
id: props.ChannelId
|
id: props.ChannelId
|
||||||
});
|
});
|
||||||
|
|
||||||
AppDispatcher.handleViewAction({
|
|
||||||
type: ActionTypes.CLICK_TEAM,
|
|
||||||
id: props.TeamId
|
|
||||||
});
|
|
||||||
|
|
||||||
AsyncClient.getAllPreferences();
|
AsyncClient.getAllPreferences();
|
||||||
|
|
||||||
// ChannelLoader must be rendered first
|
// ChannelLoader must be rendered first
|
||||||
@@ -237,7 +229,7 @@ function setupChannelPage(props) {
|
|||||||
document.getElementById('register_app_modal')
|
document.getElementById('register_app_modal')
|
||||||
);
|
);
|
||||||
|
|
||||||
if (global.window.config.SendEmailNotifications === 'false') {
|
if (global.window.mm_config.SendEmailNotifications === 'false') {
|
||||||
ErrorStore.storeLastError({message: 'Preview Mode: Email notifications have not been configured'});
|
ErrorStore.storeLastError({message: 'Preview Mode: Email notifications have not been configured'});
|
||||||
ErrorStore.emitChange();
|
ErrorStore.emitChange();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,15 @@
|
|||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
var ChannelStore = require('../stores/channel_store.jsx');
|
var ChannelStore = require('../stores/channel_store.jsx');
|
||||||
|
var TeamStore = require('../stores/team_store.jsx');
|
||||||
var Constants = require('../utils/constants.jsx');
|
var Constants = require('../utils/constants.jsx');
|
||||||
|
|
||||||
function setupHomePage(props) {
|
function setupHomePage() {
|
||||||
var last = ChannelStore.getLastVisitedName();
|
var last = ChannelStore.getLastVisitedName();
|
||||||
if (last == null || last.length === 0) {
|
if (last == null || last.length === 0) {
|
||||||
window.location = props.TeamURL + '/channels/' + Constants.DEFAULT_CHANNEL;
|
window.location = TeamStore.getCurrentTeamUrl() + '/channels/' + Constants.DEFAULT_CHANNEL;
|
||||||
} else {
|
} else {
|
||||||
window.location = props.TeamURL + '/channels/' + last;
|
window.location = TeamStore.getCurrentTeamUrl() + '/channels/' + last;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
var UserStore;
|
|
||||||
function getPrefix() {
|
function getPrefix() {
|
||||||
if (!UserStore) {
|
if (global.window.mm_user) {
|
||||||
UserStore = require('./user_store.jsx'); //eslint-disable-line global-require
|
return global.window.mm_user.id + '_';
|
||||||
}
|
}
|
||||||
return UserStore.getCurrentId() + '_';
|
|
||||||
|
return 'unknown_';
|
||||||
}
|
}
|
||||||
|
|
||||||
class BrowserStoreClass {
|
class BrowserStoreClass {
|
||||||
@@ -17,35 +17,55 @@ class BrowserStoreClass {
|
|||||||
this.setGlobalItem = this.setGlobalItem.bind(this);
|
this.setGlobalItem = this.setGlobalItem.bind(this);
|
||||||
this.getGlobalItem = this.getGlobalItem.bind(this);
|
this.getGlobalItem = this.getGlobalItem.bind(this);
|
||||||
this.removeGlobalItem = this.removeGlobalItem.bind(this);
|
this.removeGlobalItem = this.removeGlobalItem.bind(this);
|
||||||
this.clear = this.clear.bind(this);
|
|
||||||
this.actionOnItemsWithPrefix = this.actionOnItemsWithPrefix.bind(this);
|
this.actionOnItemsWithPrefix = this.actionOnItemsWithPrefix.bind(this);
|
||||||
|
this.actionOnGlobalItemsWithPrefix = this.actionOnGlobalItemsWithPrefix.bind(this);
|
||||||
this.isLocalStorageSupported = this.isLocalStorageSupported.bind(this);
|
this.isLocalStorageSupported = this.isLocalStorageSupported.bind(this);
|
||||||
|
this.getLastServerVersion = this.getLastServerVersion.bind(this);
|
||||||
|
this.setLastServerVersion = this.setLastServerVersion.bind(this);
|
||||||
|
this.clear = this.clear.bind(this);
|
||||||
|
this.clearAll = this.clearAll.bind(this);
|
||||||
|
|
||||||
var currentVersion = localStorage.getItem('local_storage_version');
|
var currentVersion = sessionStorage.getItem('storage_version');
|
||||||
if (currentVersion !== global.window.config.Version) {
|
if (currentVersion !== global.window.mm_config.Version) {
|
||||||
this.clear();
|
sessionStorage.clear();
|
||||||
localStorage.setItem('local_storage_version', global.window.config.Version);
|
sessionStorage.setItem('storage_version', global.window.mm_config.Version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getItem(name, defaultValue) {
|
getItem(name, defaultValue) {
|
||||||
return this.getGlobalItem(getPrefix() + name, defaultValue);
|
var result = null;
|
||||||
|
try {
|
||||||
|
result = JSON.parse(sessionStorage.getItem(getPrefix() + name));
|
||||||
|
} catch (err) {
|
||||||
|
result = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result === null && typeof defaultValue !== 'undefined') {
|
||||||
|
result = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
setItem(name, value) {
|
setItem(name, value) {
|
||||||
this.setGlobalItem(getPrefix() + name, value);
|
sessionStorage.setItem(getPrefix() + name, JSON.stringify(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
removeItem(name) {
|
removeItem(name) {
|
||||||
localStorage.removeItem(getPrefix() + name);
|
sessionStorage.removeItem(getPrefix() + name);
|
||||||
}
|
}
|
||||||
|
|
||||||
setGlobalItem(name, value) {
|
setGlobalItem(name, value) {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(name, JSON.stringify(value));
|
if (this.isLocalStorageSupported()) {
|
||||||
|
localStorage.setItem(getPrefix() + name, JSON.stringify(value));
|
||||||
|
} else {
|
||||||
|
sessionStorage.setItem(getPrefix() + name, JSON.stringify(value));
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('An error occurred while setting local storage, clearing all props'); //eslint-disable-line no-console
|
console.log('An error occurred while setting local storage, clearing all props'); //eslint-disable-line no-console
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
|
sessionStorage.clear();
|
||||||
window.location.href = window.location.href;
|
window.location.href = window.location.href;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,7 +73,11 @@ class BrowserStoreClass {
|
|||||||
getGlobalItem(name, defaultValue) {
|
getGlobalItem(name, defaultValue) {
|
||||||
var result = null;
|
var result = null;
|
||||||
try {
|
try {
|
||||||
result = JSON.parse(localStorage.getItem(name));
|
if (this.isLocalStorageSupported()) {
|
||||||
|
result = JSON.parse(getPrefix() + localStorage.getItem(name));
|
||||||
|
} else {
|
||||||
|
result = JSON.parse(getPrefix() + sessionStorage.getItem(name));
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
result = null;
|
result = null;
|
||||||
}
|
}
|
||||||
@@ -66,22 +90,35 @@ class BrowserStoreClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeGlobalItem(name) {
|
removeGlobalItem(name) {
|
||||||
localStorage.removeItem(name);
|
if (this.isLocalStorageSupported()) {
|
||||||
|
localStorage.removeItem(getPrefix() + name);
|
||||||
|
} else {
|
||||||
|
sessionStorage.removeItem(getPrefix() + name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
getLastServerVersion() {
|
||||||
localStorage.clear();
|
return sessionStorage.getItem('last_server_version');
|
||||||
sessionStorage.clear();
|
}
|
||||||
|
|
||||||
|
setLastServerVersion(version) {
|
||||||
|
sessionStorage.setItem('last_server_version', version);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preforms the given action on each item that has the given prefix
|
* Preforms the given action on each item that has the given prefix
|
||||||
* Signature for action is action(key, value)
|
* Signature for action is action(key, value)
|
||||||
*/
|
*/
|
||||||
actionOnItemsWithPrefix(prefix, action) {
|
actionOnGlobalItemsWithPrefix(prefix, action) {
|
||||||
var globalPrefix = getPrefix();
|
var globalPrefix = getPrefix();
|
||||||
var globalPrefixiLen = globalPrefix.length;
|
var globalPrefixiLen = globalPrefix.length;
|
||||||
for (var key in localStorage) {
|
|
||||||
|
var storage = sessionStorage;
|
||||||
|
if (this.isLocalStorageSupported()) {
|
||||||
|
storage = localStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var key in storage) {
|
||||||
if (key.lastIndexOf(globalPrefix + prefix, 0) === 0) {
|
if (key.lastIndexOf(globalPrefix + prefix, 0) === 0) {
|
||||||
var userkey = key.substring(globalPrefixiLen);
|
var userkey = key.substring(globalPrefixiLen);
|
||||||
action(userkey, this.getGlobalItem(key));
|
action(userkey, this.getGlobalItem(key));
|
||||||
@@ -89,6 +126,26 @@ class BrowserStoreClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actionOnItemsWithPrefix(prefix, action) {
|
||||||
|
var globalPrefix = getPrefix();
|
||||||
|
var globalPrefixiLen = globalPrefix.length;
|
||||||
|
for (var key in sessionStorage) {
|
||||||
|
if (key.lastIndexOf(globalPrefix + prefix, 0) === 0) {
|
||||||
|
var userkey = key.substring(globalPrefixiLen);
|
||||||
|
action(userkey, this.getGlobalItem(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
sessionStorage.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAll() {
|
||||||
|
sessionStorage.clear();
|
||||||
|
localStorage.clear();
|
||||||
|
}
|
||||||
|
|
||||||
isLocalStorageSupported() {
|
isLocalStorageSupported() {
|
||||||
try {
|
try {
|
||||||
sessionStorage.setItem('testSession', '1');
|
sessionStorage.setItem('testSession', '1');
|
||||||
|
|||||||
@@ -34,9 +34,11 @@ class ErrorStoreClass extends EventEmitter {
|
|||||||
removeChangeListener(callback) {
|
removeChangeListener(callback) {
|
||||||
this.removeListener(CHANGE_EVENT, callback);
|
this.removeListener(CHANGE_EVENT, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
handledError() {
|
handledError() {
|
||||||
BrowserStore.removeItem('last_error');
|
BrowserStore.removeItem('last_error');
|
||||||
}
|
}
|
||||||
|
|
||||||
getLastError() {
|
getLastError() {
|
||||||
return BrowserStore.getItem('last_error');
|
return BrowserStore.getItem('last_error');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -324,10 +324,10 @@ class PostStoreClass extends EventEmitter {
|
|||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
BrowserStore.setItem('pending_posts_' + channelId, postList);
|
BrowserStore.setGlobalItem('pending_posts_' + channelId, postList);
|
||||||
}
|
}
|
||||||
getPendingPosts(channelId) {
|
getPendingPosts(channelId) {
|
||||||
return BrowserStore.getItem('pending_posts_' + channelId);
|
return BrowserStore.getGlobalItem('pending_posts_' + channelId);
|
||||||
}
|
}
|
||||||
storeUnseenDeletedPost(post) {
|
storeUnseenDeletedPost(post) {
|
||||||
var posts = this.getUnseenDeletedPosts(post.channel_id);
|
var posts = this.getUnseenDeletedPosts(post.channel_id);
|
||||||
@@ -371,7 +371,7 @@ class PostStoreClass extends EventEmitter {
|
|||||||
this.pStorePendingPosts(channelId, postList);
|
this.pStorePendingPosts(channelId, postList);
|
||||||
}
|
}
|
||||||
clearPendingPosts() {
|
clearPendingPosts() {
|
||||||
BrowserStore.actionOnItemsWithPrefix('pending_posts_', function clearPending(key) {
|
BrowserStore.actionOnGlobalItemsWithPrefix('pending_posts_', function clearPending(key) {
|
||||||
BrowserStore.removeItem(key);
|
BrowserStore.removeItem(key);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -414,26 +414,26 @@ class PostStoreClass extends EventEmitter {
|
|||||||
}
|
}
|
||||||
storeCurrentDraft(draft) {
|
storeCurrentDraft(draft) {
|
||||||
var channelId = ChannelStore.getCurrentId();
|
var channelId = ChannelStore.getCurrentId();
|
||||||
BrowserStore.setItem('draft_' + channelId, draft);
|
BrowserStore.setGlobalItem('draft_' + channelId, draft);
|
||||||
}
|
}
|
||||||
getCurrentDraft() {
|
getCurrentDraft() {
|
||||||
var channelId = ChannelStore.getCurrentId();
|
var channelId = ChannelStore.getCurrentId();
|
||||||
return this.getDraft(channelId);
|
return this.getDraft(channelId);
|
||||||
}
|
}
|
||||||
storeDraft(channelId, draft) {
|
storeDraft(channelId, draft) {
|
||||||
BrowserStore.setItem('draft_' + channelId, draft);
|
BrowserStore.setGlobalItem('draft_' + channelId, draft);
|
||||||
}
|
}
|
||||||
getDraft(channelId) {
|
getDraft(channelId) {
|
||||||
return BrowserStore.getItem('draft_' + channelId, this.getEmptyDraft());
|
return BrowserStore.getGlobalItem('draft_' + channelId, this.getEmptyDraft());
|
||||||
}
|
}
|
||||||
storeCommentDraft(parentPostId, draft) {
|
storeCommentDraft(parentPostId, draft) {
|
||||||
BrowserStore.setItem('comment_draft_' + parentPostId, draft);
|
BrowserStore.setGlobalItem('comment_draft_' + parentPostId, draft);
|
||||||
}
|
}
|
||||||
getCommentDraft(parentPostId) {
|
getCommentDraft(parentPostId) {
|
||||||
return BrowserStore.getItem('comment_draft_' + parentPostId, this.getEmptyDraft());
|
return BrowserStore.getGlobalItem('comment_draft_' + parentPostId, this.getEmptyDraft());
|
||||||
}
|
}
|
||||||
clearDraftUploads() {
|
clearDraftUploads() {
|
||||||
BrowserStore.actionOnItemsWithPrefix('draft_', function clearUploads(key, value) {
|
BrowserStore.actionOnGlobalItemsWithPrefix('draft_', function clearUploads(key, value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
value.uploadsInProgress = [];
|
value.uploadsInProgress = [];
|
||||||
BrowserStore.setItem(key, value);
|
BrowserStore.setItem(key, value);
|
||||||
@@ -441,7 +441,7 @@ class PostStoreClass extends EventEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
clearCommentDraftUploads() {
|
clearCommentDraftUploads() {
|
||||||
BrowserStore.actionOnItemsWithPrefix('comment_draft_', function clearUploads(key, value) {
|
BrowserStore.actionOnGlobalItemsWithPrefix('comment_draft_', function clearUploads(key, value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
value.uploadsInProgress = [];
|
value.uploadsInProgress = [];
|
||||||
BrowserStore.setItem(key, value);
|
BrowserStore.setItem(key, value);
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ class SocketStoreClass extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!global.window.mm_session_token_index) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.setMaxListeners(0);
|
this.setMaxListeners(0);
|
||||||
|
|
||||||
if (window.WebSocket && !conn) {
|
if (window.WebSocket && !conn) {
|
||||||
@@ -45,7 +49,9 @@ class SocketStoreClass extends EventEmitter {
|
|||||||
if (window.location.protocol === 'https:') {
|
if (window.location.protocol === 'https:') {
|
||||||
protocol = 'wss://';
|
protocol = 'wss://';
|
||||||
}
|
}
|
||||||
var connUrl = protocol + location.host + '/api/v1/websocket';
|
|
||||||
|
var connUrl = protocol + location.host + '/api/v1/websocket?' + Utils.getSessionIndex();
|
||||||
|
|
||||||
if (this.failCount === 0) {
|
if (this.failCount === 0) {
|
||||||
console.log('websocket connecting to ' + connUrl); //eslint-disable-line no-console
|
console.log('websocket connecting to ' + connUrl); //eslint-disable-line no-console
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,29 +28,31 @@ class TeamStoreClass extends EventEmitter {
|
|||||||
this.get = this.get.bind(this);
|
this.get = this.get.bind(this);
|
||||||
this.getByName = this.getByName.bind(this);
|
this.getByName = this.getByName.bind(this);
|
||||||
this.getAll = this.getAll.bind(this);
|
this.getAll = this.getAll.bind(this);
|
||||||
this.setCurrentId = this.setCurrentId.bind(this);
|
|
||||||
this.getCurrentId = this.getCurrentId.bind(this);
|
this.getCurrentId = this.getCurrentId.bind(this);
|
||||||
this.getCurrent = this.getCurrent.bind(this);
|
this.getCurrent = this.getCurrent.bind(this);
|
||||||
this.getCurrentTeamUrl = this.getCurrentTeamUrl.bind(this);
|
this.getCurrentTeamUrl = this.getCurrentTeamUrl.bind(this);
|
||||||
this.storeTeam = this.storeTeam.bind(this);
|
this.saveTeam = this.saveTeam.bind(this);
|
||||||
this.pStoreTeams = this.pStoreTeams.bind(this);
|
|
||||||
this.pGetTeams = this.pGetTeams.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emitChange() {
|
emitChange() {
|
||||||
this.emit(CHANGE_EVENT);
|
this.emit(CHANGE_EVENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
addChangeListener(callback) {
|
addChangeListener(callback) {
|
||||||
this.on(CHANGE_EVENT, callback);
|
this.on(CHANGE_EVENT, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeChangeListener(callback) {
|
removeChangeListener(callback) {
|
||||||
this.removeListener(CHANGE_EVENT, callback);
|
this.removeListener(CHANGE_EVENT, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
get(id) {
|
get(id) {
|
||||||
var c = this.pGetTeams();
|
var c = this.getAll();
|
||||||
return c[id];
|
return c[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
getByName(name) {
|
getByName(name) {
|
||||||
var t = this.pGetTeams();
|
var t = this.getAll();
|
||||||
|
|
||||||
for (var id in t) {
|
for (var id in t) {
|
||||||
if (t[id].name === name) {
|
if (t[id].name === name) {
|
||||||
@@ -60,59 +62,51 @@ class TeamStoreClass extends EventEmitter {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
getAll() {
|
|
||||||
return this.pGetTeams();
|
|
||||||
}
|
|
||||||
setCurrentId(id) {
|
|
||||||
if (id === null) {
|
|
||||||
BrowserStore.removeItem('current_team_id');
|
|
||||||
} else {
|
|
||||||
BrowserStore.setItem('current_team_id', id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
getCurrentId() {
|
|
||||||
return BrowserStore.getItem('current_team_id');
|
|
||||||
}
|
|
||||||
getCurrent() {
|
|
||||||
var currentId = this.getCurrentId();
|
|
||||||
|
|
||||||
if (currentId !== null) {
|
getAll() {
|
||||||
return this.get(currentId);
|
return BrowserStore.getItem('user_teams', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentId() {
|
||||||
|
var team = global.window.mm_team;
|
||||||
|
|
||||||
|
if (team) {
|
||||||
|
return team.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCurrent() {
|
||||||
|
if (global.window.mm_team != null && this.get(global.window.mm_team.id) == null) {
|
||||||
|
this.saveTeam(global.window.mm_team);
|
||||||
|
}
|
||||||
|
|
||||||
|
return global.window.mm_team;
|
||||||
|
}
|
||||||
|
|
||||||
getCurrentTeamUrl() {
|
getCurrentTeamUrl() {
|
||||||
if (this.getCurrent()) {
|
if (this.getCurrent()) {
|
||||||
return getWindowLocationOrigin() + '/' + this.getCurrent().name;
|
return getWindowLocationOrigin() + '/' + this.getCurrent().name;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
storeTeam(team) {
|
|
||||||
var teams = this.pGetTeams();
|
saveTeam(team) {
|
||||||
|
var teams = this.getAll();
|
||||||
teams[team.id] = team;
|
teams[team.id] = team;
|
||||||
this.pStoreTeams(teams);
|
|
||||||
}
|
|
||||||
pStoreTeams(teams) {
|
|
||||||
BrowserStore.setItem('user_teams', teams);
|
BrowserStore.setItem('user_teams', teams);
|
||||||
}
|
}
|
||||||
pGetTeams() {
|
|
||||||
return BrowserStore.getItem('user_teams', {});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var TeamStore = new TeamStoreClass();
|
var TeamStore = new TeamStoreClass();
|
||||||
|
|
||||||
TeamStore.dispatchToken = AppDispatcher.register(function registry(payload) {
|
TeamStore.dispatchToken = AppDispatcher.register((payload) => {
|
||||||
var action = payload.action;
|
var action = payload.action;
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ActionTypes.CLICK_TEAM:
|
|
||||||
TeamStore.setCurrentId(action.id);
|
|
||||||
TeamStore.emitChange();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ActionTypes.RECIEVED_TEAM:
|
case ActionTypes.RECIEVED_TEAM:
|
||||||
TeamStore.storeTeam(action.team);
|
TeamStore.saveTeam(action.team);
|
||||||
TeamStore.emitChange();
|
TeamStore.emitChange();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
||||||
var EventEmitter = require('events').EventEmitter;
|
var EventEmitter = require('events').EventEmitter;
|
||||||
var client = require('../utils/client.jsx');
|
|
||||||
|
|
||||||
var Constants = require('../utils/constants.jsx');
|
var Constants = require('../utils/constants.jsx');
|
||||||
var ActionTypes = Constants.ActionTypes;
|
var ActionTypes = Constants.ActionTypes;
|
||||||
@@ -38,13 +37,11 @@ class UserStoreClass extends EventEmitter {
|
|||||||
this.emitToggleImportModal = this.emitToggleImportModal.bind(this);
|
this.emitToggleImportModal = this.emitToggleImportModal.bind(this);
|
||||||
this.addImportModalListener = this.addImportModalListener.bind(this);
|
this.addImportModalListener = this.addImportModalListener.bind(this);
|
||||||
this.removeImportModalListener = this.removeImportModalListener.bind(this);
|
this.removeImportModalListener = this.removeImportModalListener.bind(this);
|
||||||
this.setCurrentId = this.setCurrentId.bind(this);
|
|
||||||
this.getCurrentId = this.getCurrentId.bind(this);
|
this.getCurrentId = this.getCurrentId.bind(this);
|
||||||
this.getCurrentUser = this.getCurrentUser.bind(this);
|
this.getCurrentUser = this.getCurrentUser.bind(this);
|
||||||
this.setCurrentUser = this.setCurrentUser.bind(this);
|
this.setCurrentUser = this.setCurrentUser.bind(this);
|
||||||
this.getLastEmail = this.getLastEmail.bind(this);
|
this.getLastEmail = this.getLastEmail.bind(this);
|
||||||
this.setLastEmail = this.setLastEmail.bind(this);
|
this.setLastEmail = this.setLastEmail.bind(this);
|
||||||
this.removeCurrentUser = this.removeCurrentUser.bind(this);
|
|
||||||
this.hasProfile = this.hasProfile.bind(this);
|
this.hasProfile = this.hasProfile.bind(this);
|
||||||
this.getProfile = this.getProfile.bind(this);
|
this.getProfile = this.getProfile.bind(this);
|
||||||
this.getProfileByUsername = this.getProfileByUsername.bind(this);
|
this.getProfileByUsername = this.getProfileByUsername.bind(this);
|
||||||
@@ -52,9 +49,6 @@ class UserStoreClass extends EventEmitter {
|
|||||||
this.getProfiles = this.getProfiles.bind(this);
|
this.getProfiles = this.getProfiles.bind(this);
|
||||||
this.getActiveOnlyProfiles = this.getActiveOnlyProfiles.bind(this);
|
this.getActiveOnlyProfiles = this.getActiveOnlyProfiles.bind(this);
|
||||||
this.saveProfile = this.saveProfile.bind(this);
|
this.saveProfile = this.saveProfile.bind(this);
|
||||||
this.pStoreProfiles = this.pStoreProfiles.bind(this);
|
|
||||||
this.pGetProfiles = this.pGetProfiles.bind(this);
|
|
||||||
this.pGetProfilesUsernameMap = this.pGetProfilesUsernameMap.bind(this);
|
|
||||||
this.setSessions = this.setSessions.bind(this);
|
this.setSessions = this.setSessions.bind(this);
|
||||||
this.getSessions = this.getSessions.bind(this);
|
this.getSessions = this.getSessions.bind(this);
|
||||||
this.setAudits = this.setAudits.bind(this);
|
this.setAudits = this.setAudits.bind(this);
|
||||||
@@ -62,138 +56,155 @@ class UserStoreClass extends EventEmitter {
|
|||||||
this.setTeams = this.setTeams.bind(this);
|
this.setTeams = this.setTeams.bind(this);
|
||||||
this.getTeams = this.getTeams.bind(this);
|
this.getTeams = this.getTeams.bind(this);
|
||||||
this.getCurrentMentionKeys = this.getCurrentMentionKeys.bind(this);
|
this.getCurrentMentionKeys = this.getCurrentMentionKeys.bind(this);
|
||||||
this.getLastVersion = this.getLastVersion.bind(this);
|
|
||||||
this.setLastVersion = this.setLastVersion.bind(this);
|
|
||||||
this.setStatuses = this.setStatuses.bind(this);
|
this.setStatuses = this.setStatuses.bind(this);
|
||||||
this.pSetStatuses = this.pSetStatuses.bind(this);
|
this.pSetStatuses = this.pSetStatuses.bind(this);
|
||||||
this.setStatus = this.setStatus.bind(this);
|
this.setStatus = this.setStatus.bind(this);
|
||||||
this.getStatuses = this.getStatuses.bind(this);
|
this.getStatuses = this.getStatuses.bind(this);
|
||||||
this.getStatus = this.getStatus.bind(this);
|
this.getStatus = this.getStatus.bind(this);
|
||||||
|
|
||||||
this.gCurrentId = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emitChange(userId) {
|
emitChange(userId) {
|
||||||
this.emit(CHANGE_EVENT, userId);
|
this.emit(CHANGE_EVENT, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
addChangeListener(callback) {
|
addChangeListener(callback) {
|
||||||
this.on(CHANGE_EVENT, callback);
|
this.on(CHANGE_EVENT, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeChangeListener(callback) {
|
removeChangeListener(callback) {
|
||||||
this.removeListener(CHANGE_EVENT, callback);
|
this.removeListener(CHANGE_EVENT, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
emitSessionsChange() {
|
emitSessionsChange() {
|
||||||
this.emit(CHANGE_EVENT_SESSIONS);
|
this.emit(CHANGE_EVENT_SESSIONS);
|
||||||
}
|
}
|
||||||
|
|
||||||
addSessionsChangeListener(callback) {
|
addSessionsChangeListener(callback) {
|
||||||
this.on(CHANGE_EVENT_SESSIONS, callback);
|
this.on(CHANGE_EVENT_SESSIONS, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeSessionsChangeListener(callback) {
|
removeSessionsChangeListener(callback) {
|
||||||
this.removeListener(CHANGE_EVENT_SESSIONS, callback);
|
this.removeListener(CHANGE_EVENT_SESSIONS, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
emitAuditsChange() {
|
emitAuditsChange() {
|
||||||
this.emit(CHANGE_EVENT_AUDITS);
|
this.emit(CHANGE_EVENT_AUDITS);
|
||||||
}
|
}
|
||||||
|
|
||||||
addAuditsChangeListener(callback) {
|
addAuditsChangeListener(callback) {
|
||||||
this.on(CHANGE_EVENT_AUDITS, callback);
|
this.on(CHANGE_EVENT_AUDITS, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAuditsChangeListener(callback) {
|
removeAuditsChangeListener(callback) {
|
||||||
this.removeListener(CHANGE_EVENT_AUDITS, callback);
|
this.removeListener(CHANGE_EVENT_AUDITS, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
emitTeamsChange() {
|
emitTeamsChange() {
|
||||||
this.emit(CHANGE_EVENT_TEAMS);
|
this.emit(CHANGE_EVENT_TEAMS);
|
||||||
}
|
}
|
||||||
|
|
||||||
addTeamsChangeListener(callback) {
|
addTeamsChangeListener(callback) {
|
||||||
this.on(CHANGE_EVENT_TEAMS, callback);
|
this.on(CHANGE_EVENT_TEAMS, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeTeamsChangeListener(callback) {
|
removeTeamsChangeListener(callback) {
|
||||||
this.removeListener(CHANGE_EVENT_TEAMS, callback);
|
this.removeListener(CHANGE_EVENT_TEAMS, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
emitStatusesChange() {
|
emitStatusesChange() {
|
||||||
this.emit(CHANGE_EVENT_STATUSES);
|
this.emit(CHANGE_EVENT_STATUSES);
|
||||||
}
|
}
|
||||||
|
|
||||||
addStatusesChangeListener(callback) {
|
addStatusesChangeListener(callback) {
|
||||||
this.on(CHANGE_EVENT_STATUSES, callback);
|
this.on(CHANGE_EVENT_STATUSES, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeStatusesChangeListener(callback) {
|
removeStatusesChangeListener(callback) {
|
||||||
this.removeListener(CHANGE_EVENT_STATUSES, callback);
|
this.removeListener(CHANGE_EVENT_STATUSES, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
emitToggleImportModal(value) {
|
emitToggleImportModal(value) {
|
||||||
this.emit(TOGGLE_IMPORT_MODAL_EVENT, value);
|
this.emit(TOGGLE_IMPORT_MODAL_EVENT, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
addImportModalListener(callback) {
|
addImportModalListener(callback) {
|
||||||
this.on(TOGGLE_IMPORT_MODAL_EVENT, callback);
|
this.on(TOGGLE_IMPORT_MODAL_EVENT, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeImportModalListener(callback) {
|
removeImportModalListener(callback) {
|
||||||
this.removeListener(TOGGLE_IMPORT_MODAL_EVENT, callback);
|
this.removeListener(TOGGLE_IMPORT_MODAL_EVENT, callback);
|
||||||
}
|
}
|
||||||
setCurrentId(id) {
|
|
||||||
this.gCurrentId = id;
|
getCurrentUser() {
|
||||||
if (id == null) {
|
if (this.getProfiles()[global.window.mm_user.id] == null) {
|
||||||
BrowserStore.removeGlobalItem('current_user_id');
|
this.saveProfile(global.window.mm_user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return global.window.mm_user;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentUser(user) {
|
||||||
|
var oldUser = global.window.mm_user;
|
||||||
|
|
||||||
|
if (oldUser.id === user.id) {
|
||||||
|
global.window.mm_user = user;
|
||||||
|
this.saveProfile(user);
|
||||||
} else {
|
} else {
|
||||||
BrowserStore.setGlobalItem('current_user_id', id);
|
throw new Error('Problem with setCurrentUser old_user_id=' + oldUser.id + ' new_user_id=' + user.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getCurrentId(skipFetch) {
|
|
||||||
var currentId = this.gCurrentId;
|
|
||||||
|
|
||||||
if (currentId == null) {
|
getCurrentId() {
|
||||||
currentId = BrowserStore.getGlobalItem('current_user_id');
|
var user = global.window.mm_user;
|
||||||
this.gCurrentId = currentId;
|
|
||||||
|
if (user) {
|
||||||
|
return user.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is a special case to force fetch the
|
return null;
|
||||||
// current user if it's missing
|
}
|
||||||
// it's synchronous to block rendering
|
|
||||||
if (currentId == null && !skipFetch) {
|
getLastEmail() {
|
||||||
var me = client.getMeSynchronous();
|
return BrowserStore.getGlobalItem('last_email', '');
|
||||||
if (me != null) {
|
}
|
||||||
this.setCurrentUser(me);
|
|
||||||
currentId = me.id;
|
setLastEmail(email) {
|
||||||
|
BrowserStore.setGlobalItem('last_email', email);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasProfile(userId) {
|
||||||
|
return this.getProfiles()[userId] != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getProfile(userId) {
|
||||||
|
return this.getProfiles()[userId];
|
||||||
|
}
|
||||||
|
|
||||||
|
getProfileByUsername(username) {
|
||||||
|
return this.getProfilesUsernameMap()[username];
|
||||||
|
}
|
||||||
|
|
||||||
|
getProfilesUsernameMap() {
|
||||||
|
var profileUsernameMap = {};
|
||||||
|
|
||||||
|
var profiles = this.getProfiles();
|
||||||
|
for (var key in profiles) {
|
||||||
|
if (profiles.hasOwnProperty(key)) {
|
||||||
|
var profile = profiles[key];
|
||||||
|
profileUsernameMap[profile.username] = profile;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentId;
|
return profileUsernameMap;
|
||||||
}
|
}
|
||||||
getCurrentUser() {
|
|
||||||
if (this.getCurrentId() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.pGetProfiles()[this.getCurrentId()];
|
|
||||||
}
|
|
||||||
setCurrentUser(user) {
|
|
||||||
this.setCurrentId(user.id);
|
|
||||||
this.saveProfile(user);
|
|
||||||
}
|
|
||||||
getLastEmail() {
|
|
||||||
return BrowserStore.getItem('last_email', '');
|
|
||||||
}
|
|
||||||
setLastEmail(email) {
|
|
||||||
BrowserStore.setItem('last_email', email);
|
|
||||||
}
|
|
||||||
removeCurrentUser() {
|
|
||||||
this.setCurrentId(null);
|
|
||||||
}
|
|
||||||
hasProfile(userId) {
|
|
||||||
return this.pGetProfiles()[userId] != null;
|
|
||||||
}
|
|
||||||
getProfile(userId) {
|
|
||||||
return this.pGetProfiles()[userId];
|
|
||||||
}
|
|
||||||
getProfileByUsername(username) {
|
|
||||||
return this.pGetProfilesUsernameMap()[username];
|
|
||||||
}
|
|
||||||
getProfilesUsernameMap() {
|
|
||||||
return this.pGetProfilesUsernameMap();
|
|
||||||
}
|
|
||||||
getProfiles() {
|
getProfiles() {
|
||||||
return this.pGetProfiles();
|
return BrowserStore.getItem('profiles', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
getActiveOnlyProfiles() {
|
getActiveOnlyProfiles() {
|
||||||
var active = {};
|
var active = {};
|
||||||
var current = this.pGetProfiles();
|
var current = this.getProfiles();
|
||||||
|
|
||||||
for (var key in current) {
|
for (var key in current) {
|
||||||
if (current[key].delete_at === 0) {
|
if (current[key].delete_at === 0) {
|
||||||
@@ -203,45 +214,37 @@ class UserStoreClass extends EventEmitter {
|
|||||||
|
|
||||||
return active;
|
return active;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveProfile(profile) {
|
saveProfile(profile) {
|
||||||
var ps = this.pGetProfiles();
|
var ps = this.getProfiles();
|
||||||
ps[profile.id] = profile;
|
ps[profile.id] = profile;
|
||||||
this.pStoreProfiles(ps);
|
BrowserStore.setItem('profiles', ps);
|
||||||
}
|
|
||||||
pStoreProfiles(profiles) {
|
|
||||||
BrowserStore.setItem('profiles', profiles);
|
|
||||||
var profileUsernameMap = {};
|
|
||||||
for (var id in profiles) {
|
|
||||||
if (profiles.hasOwnProperty(id)) {
|
|
||||||
profileUsernameMap[profiles[id].username] = profiles[id];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BrowserStore.setItem('profileUsernameMap', profileUsernameMap);
|
|
||||||
}
|
|
||||||
pGetProfiles() {
|
|
||||||
return BrowserStore.getItem('profiles', {});
|
|
||||||
}
|
|
||||||
pGetProfilesUsernameMap() {
|
|
||||||
return BrowserStore.getItem('profileUsernameMap', {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setSessions(sessions) {
|
setSessions(sessions) {
|
||||||
BrowserStore.setItem('sessions', sessions);
|
BrowserStore.setItem('sessions', sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSessions() {
|
getSessions() {
|
||||||
return BrowserStore.getItem('sessions', {loading: true});
|
return BrowserStore.getItem('sessions', {loading: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
setAudits(audits) {
|
setAudits(audits) {
|
||||||
BrowserStore.setItem('audits', audits);
|
BrowserStore.setItem('audits', audits);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAudits() {
|
getAudits() {
|
||||||
return BrowserStore.getItem('audits', {loading: true});
|
return BrowserStore.getItem('audits', {loading: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
setTeams(teams) {
|
setTeams(teams) {
|
||||||
BrowserStore.setItem('teams', teams);
|
BrowserStore.setItem('teams', teams);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTeams() {
|
getTeams() {
|
||||||
return BrowserStore.getItem('teams', []);
|
return BrowserStore.getItem('teams', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentMentionKeys() {
|
getCurrentMentionKeys() {
|
||||||
var user = this.getCurrentUser();
|
var user = this.getCurrentUser();
|
||||||
|
|
||||||
@@ -269,28 +272,27 @@ class UserStoreClass extends EventEmitter {
|
|||||||
|
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
getLastVersion() {
|
|
||||||
return BrowserStore.getItem('last_version', '');
|
|
||||||
}
|
|
||||||
setLastVersion(version) {
|
|
||||||
BrowserStore.setItem('last_version', version);
|
|
||||||
}
|
|
||||||
setStatuses(statuses) {
|
setStatuses(statuses) {
|
||||||
this.pSetStatuses(statuses);
|
this.pSetStatuses(statuses);
|
||||||
this.emitStatusesChange();
|
this.emitStatusesChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
pSetStatuses(statuses) {
|
pSetStatuses(statuses) {
|
||||||
BrowserStore.setItem('statuses', statuses);
|
BrowserStore.setItem('statuses', statuses);
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatus(userId, status) {
|
setStatus(userId, status) {
|
||||||
var statuses = this.getStatuses();
|
var statuses = this.getStatuses();
|
||||||
statuses[userId] = status;
|
statuses[userId] = status;
|
||||||
this.pSetStatuses(statuses);
|
this.pSetStatuses(statuses);
|
||||||
this.emitStatusesChange();
|
this.emitStatusesChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
getStatuses() {
|
getStatuses() {
|
||||||
return BrowserStore.getItem('statuses', {});
|
return BrowserStore.getItem('statuses', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
getStatus(id) {
|
getStatus(id) {
|
||||||
return this.getStatuses()[id];
|
return this.getStatuses()[id];
|
||||||
}
|
}
|
||||||
@@ -299,7 +301,7 @@ class UserStoreClass extends EventEmitter {
|
|||||||
var UserStore = new UserStoreClass();
|
var UserStore = new UserStoreClass();
|
||||||
UserStore.setMaxListeners(0);
|
UserStore.setMaxListeners(0);
|
||||||
|
|
||||||
UserStore.dispatchToken = AppDispatcher.register(function registry(payload) {
|
UserStore.dispatchToken = AppDispatcher.register((payload) => {
|
||||||
var action = payload.action;
|
var action = payload.action;
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
var client = require('./client.jsx');
|
var client = require('./client.jsx');
|
||||||
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
||||||
|
var BrowserStore = require('../stores/browser_store.jsx');
|
||||||
var ChannelStore = require('../stores/channel_store.jsx');
|
var ChannelStore = require('../stores/channel_store.jsx');
|
||||||
var PostStore = require('../stores/post_store.jsx');
|
var PostStore = require('../stores/post_store.jsx');
|
||||||
var UserStore = require('../stores/user_store.jsx');
|
var UserStore = require('../stores/user_store.jsx');
|
||||||
@@ -50,18 +51,18 @@ export function getChannels(force, updateLastViewed, checkVersion) {
|
|||||||
callTracker.getChannels = utils.getTimestamp();
|
callTracker.getChannels = utils.getTimestamp();
|
||||||
|
|
||||||
client.getChannels(
|
client.getChannels(
|
||||||
function getChannelsSuccess(data, textStatus, xhr) {
|
(data, textStatus, xhr) => {
|
||||||
callTracker.getChannels = 0;
|
callTracker.getChannels = 0;
|
||||||
|
|
||||||
if (checkVersion) {
|
if (checkVersion) {
|
||||||
var serverVersion = xhr.getResponseHeader('X-Version-ID');
|
var serverVersion = xhr.getResponseHeader('X-Version-ID');
|
||||||
|
|
||||||
if (!UserStore.getLastVersion()) {
|
if (!BrowserStore.getLastServerVersion()) {
|
||||||
UserStore.setLastVersion(serverVersion);
|
BrowserStore.setLastServerVersion(serverVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverVersion !== UserStore.getLastVersion()) {
|
if (serverVersion !== BrowserStore.getLastServerVersion()) {
|
||||||
UserStore.setLastVersion(serverVersion);
|
BrowserStore.setLastServerVersion(serverVersion);
|
||||||
window.location.href = window.location.href;
|
window.location.href = window.location.href;
|
||||||
console.log('Detected version update refreshing the page'); //eslint-disable-line no-console
|
console.log('Detected version update refreshing the page'); //eslint-disable-line no-console
|
||||||
}
|
}
|
||||||
@@ -77,7 +78,7 @@ export function getChannels(force, updateLastViewed, checkVersion) {
|
|||||||
members: data.members
|
members: data.members
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function getChannelsFailure(err) {
|
(err) => {
|
||||||
callTracker.getChannels = 0;
|
callTracker.getChannels = 0;
|
||||||
dispatchError(err, 'getChannels');
|
dispatchError(err, 'getChannels');
|
||||||
}
|
}
|
||||||
@@ -566,8 +567,8 @@ export function getMe() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
callTracker.getMe = utils.getTimestamp();
|
callTracker.getMe = utils.getTimestamp();
|
||||||
client.getMeSynchronous(
|
client.getMe(
|
||||||
function getMeSyncSuccess(data, textStatus, xhr) {
|
(data, textStatus, xhr) => {
|
||||||
callTracker.getMe = 0;
|
callTracker.getMe = 0;
|
||||||
|
|
||||||
if (xhr.status === 304 || !data) {
|
if (xhr.status === 304 || !data) {
|
||||||
@@ -579,7 +580,7 @@ export function getMe() {
|
|||||||
me: data
|
me: data
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function getMeSyncFailure(err) {
|
(err) => {
|
||||||
callTracker.getMe = 0;
|
callTracker.getMe = 0;
|
||||||
dispatchError(err, 'getMe');
|
dispatchError(err, 'getMe');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ var BrowserStore = require('../stores/browser_store.jsx');
|
|||||||
var TeamStore = require('../stores/team_store.jsx');
|
var TeamStore = require('../stores/team_store.jsx');
|
||||||
var ErrorStore = require('../stores/error_store.jsx');
|
var ErrorStore = require('../stores/error_store.jsx');
|
||||||
|
|
||||||
export function track(category, action, label, prop, val) {
|
export function track(category, action, label, property, value) {
|
||||||
global.window.analytics.track(action, {category: category, label: label, property: prop, value: val});
|
global.window.analytics.track(action, {category, label, property, value});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function trackPage() {
|
export function trackPage() {
|
||||||
@@ -232,6 +232,7 @@ export function logout() {
|
|||||||
track('api', 'api_users_logout');
|
track('api', 'api_users_logout');
|
||||||
var currentTeamUrl = TeamStore.getCurrentTeamUrl();
|
var currentTeamUrl = TeamStore.getCurrentTeamUrl();
|
||||||
BrowserStore.clear();
|
BrowserStore.clear();
|
||||||
|
ErrorStore.storeLastError(null);
|
||||||
window.location.href = currentTeamUrl + '/logout';
|
window.location.href = currentTeamUrl + '/logout';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,10 +386,9 @@ export function getAllTeams(success, error) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMeSynchronous(success, error) {
|
export function getMe(success, error) {
|
||||||
var currentUser = null;
|
var currentUser = null;
|
||||||
$.ajax({
|
$.ajax({
|
||||||
async: false,
|
|
||||||
cache: false,
|
cache: false,
|
||||||
url: '/api/v1/users/me',
|
url: '/api/v1/users/me',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
@@ -402,7 +402,7 @@ export function getMeSynchronous(success, error) {
|
|||||||
},
|
},
|
||||||
error: function onError(xhr, status, err) {
|
error: function onError(xhr, status, err) {
|
||||||
if (error) {
|
if (error) {
|
||||||
var e = handleError('getMeSynchronous', xhr, status, err);
|
var e = handleError('getMe', xhr, status, err);
|
||||||
error(e);
|
error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ module.exports = {
|
|||||||
|
|
||||||
RECIEVED_MSG: null,
|
RECIEVED_MSG: null,
|
||||||
|
|
||||||
CLICK_TEAM: null,
|
|
||||||
RECIEVED_TEAM: null,
|
RECIEVED_TEAM: null,
|
||||||
|
|
||||||
RECIEVED_CONFIG: null,
|
RECIEVED_CONFIG: null,
|
||||||
|
|||||||
@@ -2,27 +2,27 @@
|
|||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
const emoticonPatterns = {
|
const emoticonPatterns = {
|
||||||
smile: /(^|\s)(:-?\))($|\s)/g, // :)
|
smile: /(^|\s)(:-?\))(?=$|\s)/g, // :)
|
||||||
wink: /(^|\s)(;-?\))($|\s)/g, // ;)
|
wink: /(^|\s)(;-?\))(?=$|\s)/g, // ;)
|
||||||
open_mouth: /(^|\s)(:o)($|\s)/gi, // :o
|
open_mouth: /(^|\s)(:o)(?=$|\s)/gi, // :o
|
||||||
scream: /(^|\s)(:-o)($|\s)/gi, // :-o
|
scream: /(^|\s)(:-o)(?=$|\s)/gi, // :-o
|
||||||
smirk: /(^|\s)(:-?])($|\s)/g, // :]
|
smirk: /(^|\s)(:-?])(?=$|\s)/g, // :]
|
||||||
grinning: /(^|\s)(:-?d)($|\s)/gi, // :D
|
grinning: /(^|\s)(:-?d)(?=$|\s)/gi, // :D
|
||||||
stuck_out_tongue_closed_eyes: /(^|\s)(x-d)($|\s)/gi, // x-d
|
stuck_out_tongue_closed_eyes: /(^|\s)(x-d)(?=$|\s)/gi, // x-d
|
||||||
stuck_out_tongue: /(^|\s)(:-?p)($|\s)/gi, // :p
|
stuck_out_tongue: /(^|\s)(:-?p)(?=$|\s)/gi, // :p
|
||||||
rage: /(^|\s)(:-?[\[@])($|\s)/g, // :@
|
rage: /(^|\s)(:-?[\[@])(?=$|\s)/g, // :@
|
||||||
frowning: /(^|\s)(:-?\()($|\s)/g, // :(
|
frowning: /(^|\s)(:-?\()(?=$|\s)/g, // :(
|
||||||
sob: /(^|\s)(:['’]-?\(|:'\(|:'\()($|\s)/g, // :`(
|
sob: /(^|\s)(:['’]-?\(|:'\(|:'\()(?=$|\s)/g, // :`(
|
||||||
kissing_heart: /(^|\s)(:-?\*)($|\s)/g, // :*
|
kissing_heart: /(^|\s)(:-?\*)(?=$|\s)/g, // :*
|
||||||
pensive: /(^|\s)(:-?\/)($|\s)/g, // :/
|
pensive: /(^|\s)(:-?\/)(?=$|\s)/g, // :/
|
||||||
confounded: /(^|\s)(:-?s)($|\s)/gi, // :s
|
confounded: /(^|\s)(:-?s)(?=$|\s)/gi, // :s
|
||||||
flushed: /(^|\s)(:-?\|)($|\s)/g, // :|
|
flushed: /(^|\s)(:-?\|)(?=$|\s)/g, // :|
|
||||||
relaxed: /(^|\s)(:-?\$)($|\s)/g, // :$
|
relaxed: /(^|\s)(:-?\$)(?=$|\s)/g, // :$
|
||||||
mask: /(^|\s)(:-x)($|\s)/gi, // :-x
|
mask: /(^|\s)(:-x)(?=$|\s)/gi, // :-x
|
||||||
heart: /(^|\s)(<3|<3)($|\s)/g, // <3
|
heart: /(^|\s)(<3|<3)(?=$|\s)/g, // <3
|
||||||
broken_heart: /(^|\s)(<\/3|</3)($|\s)/g, // </3
|
broken_heart: /(^|\s)(<\/3|</3)(?=$|\s)/g, // </3
|
||||||
thumbsup: /(^|\s)(:\+1:)($|\s)/g, // :+1:
|
thumbsup: /(^|\s)(:\+1:)(?=$|\s)/g, // :+1:
|
||||||
thumbsdown: /(^|\s)(:\-1:)($|\s)/g // :-1:
|
thumbsdown: /(^|\s)(:\-1:)(?=$|\s)/g // :-1:
|
||||||
};
|
};
|
||||||
|
|
||||||
function initializeEmoticonMap() {
|
function initializeEmoticonMap() {
|
||||||
@@ -127,28 +127,28 @@ const emoticonMap = initializeEmoticonMap();
|
|||||||
export function handleEmoticons(text, tokens) {
|
export function handleEmoticons(text, tokens) {
|
||||||
let output = text;
|
let output = text;
|
||||||
|
|
||||||
function replaceEmoticonWithToken(match, prefix, name, suffix) {
|
function replaceEmoticonWithToken(fullMatch, prefix, matchText, name) {
|
||||||
if (emoticonMap[name]) {
|
if (emoticonMap[name]) {
|
||||||
const index = tokens.size;
|
const index = tokens.size;
|
||||||
const alias = `MM_EMOTICON${index}`;
|
const alias = `MM_EMOTICON${index}`;
|
||||||
|
|
||||||
tokens.set(alias, {
|
tokens.set(alias, {
|
||||||
value: `<img align="absmiddle" alt="${match}" class="emoji" src="${getImagePathForEmoticon(name)}" title="${match}" />`,
|
value: `<img align="absmiddle" alt="${matchText}" class="emoji" src="${getImagePathForEmoticon(name)}" title="${matchText}" />`,
|
||||||
originalText: match
|
originalText: fullMatch
|
||||||
});
|
});
|
||||||
|
|
||||||
return prefix + alias + suffix;
|
return prefix + alias;
|
||||||
}
|
}
|
||||||
|
|
||||||
return match;
|
return fullMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
output = output.replace(/(^|\s):([a-zA-Z0-9_-]+):($|\s)/g, replaceEmoticonWithToken);
|
output = output.replace(/(^|\s)(:([a-zA-Z0-9_-]+):)(?=$|\s)/g, (fullMatch, prefix, matchText, name) => replaceEmoticonWithToken(fullMatch, prefix, matchText, name));
|
||||||
|
|
||||||
$.each(emoticonPatterns, (name, pattern) => {
|
$.each(emoticonPatterns, (name, pattern) => {
|
||||||
// this might look a bit funny, but since the name isn't contained in the actual match
|
// this might look a bit funny, but since the name isn't contained in the actual match
|
||||||
// like with the named emoticons, we need to add it in manually
|
// like with the named emoticons, we need to add it in manually
|
||||||
output = output.replace(pattern, (match, prefix, emoticon, suffix) => replaceEmoticonWithToken(match, prefix, name, suffix));
|
output = output.replace(pattern, (fullMatch, prefix, matchText) => replaceEmoticonWithToken(fullMatch, prefix, matchText, name));
|
||||||
});
|
});
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
|
|||||||
@@ -869,7 +869,7 @@ export function getFileUrl(filename) {
|
|||||||
if (url.indexOf('/api/v1/files/get') !== -1) {
|
if (url.indexOf('/api/v1/files/get') !== -1) {
|
||||||
url = filename.split('/api/v1/files/get')[1];
|
url = filename.split('/api/v1/files/get')[1];
|
||||||
}
|
}
|
||||||
url = getWindowLocationOrigin() + '/api/v1/files/get' + url;
|
url = getWindowLocationOrigin() + '/api/v1/files/get' + url + '?' + getSessionIndex();
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
@@ -880,6 +880,14 @@ export function getFileName(path) {
|
|||||||
return split[split.length - 1];
|
return split[split.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getSessionIndex() {
|
||||||
|
if (global.window.mm_session_token_index >= 0) {
|
||||||
|
return 'session_token_index=' + global.window.mm_session_token_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
// Generates a RFC-4122 version 4 compliant globally unique identifier.
|
// Generates a RFC-4122 version 4 compliant globally unique identifier.
|
||||||
export function generateId() {
|
export function generateId() {
|
||||||
// implementation taken from http://stackoverflow.com/a/2117523
|
// implementation taken from http://stackoverflow.com/a/2117523
|
||||||
|
|||||||
@@ -20,3 +20,22 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-help-popover {
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
padding-left: 2em;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.tooltip-inner {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-help-popover.visible {
|
||||||
|
opacity: 100;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "footer"}}
|
{{define "footer"}}
|
||||||
<div class="footer-pane col-xs-12">
|
<div class="footer-pane col-xs-12">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<span class="pull-right footer-site-name">{{ .ClientProps.SiteName }}</span>
|
<span class="pull-right footer-site-name">{{ .ClientCfg.SiteName }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<span class="pull-right footer-link copyright">© 2015 Mattermost, Inc.</span>
|
<span class="pull-right footer-link copyright">© 2015 Mattermost, Inc.</span>
|
||||||
|
|||||||
@@ -18,10 +18,6 @@
|
|||||||
<link rel="manifest" href="/static/config/manifest.json">
|
<link rel="manifest" href="/static/config/manifest.json">
|
||||||
<!-- Android add to homescreen -->
|
<!-- Android add to homescreen -->
|
||||||
|
|
||||||
<script>
|
|
||||||
window.config = {{ .ClientProps }};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- CSS Should always go first -->
|
<!-- CSS Should always go first -->
|
||||||
<link rel="stylesheet" href="/static/css/bootstrap-3.3.5.min.css">
|
<link rel="stylesheet" href="/static/css/bootstrap-3.3.5.min.css">
|
||||||
<link rel="stylesheet" href="/static/css/jasny-bootstrap.min.css">
|
<link rel="stylesheet" href="/static/css/jasny-bootstrap.min.css">
|
||||||
@@ -43,6 +39,19 @@
|
|||||||
|
|
||||||
<style id="antiClickjack">body{display:none !important;}</style>
|
<style id="antiClickjack">body{display:none !important;}</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.mm_config = {{ .ClientCfg }};
|
||||||
|
window.mm_team = {{ .Team }};
|
||||||
|
window.mm_user = {{ .User }};
|
||||||
|
|
||||||
|
if ({{.SessionTokenIndex}} >= 0) {
|
||||||
|
window.mm_session_token_index = {{.SessionTokenIndex}};
|
||||||
|
$.ajaxSetup({
|
||||||
|
headers: { 'X-MM-TokenIndex': mm_session_token_index }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
window.onerror = function(msg, url, line, column, stack) {
|
window.onerror = function(msg, url, line, column, stack) {
|
||||||
var l = {};
|
var l = {};
|
||||||
@@ -70,9 +79,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
if (window.config.SegmentDeveloperKey != null && window.config.SegmentDeveloperKey !== "") {
|
if (window.mm_config.SegmentDeveloperKey != null && window.mm_config.SegmentDeveloperKey !== "") {
|
||||||
!function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","group","track","ready","alias","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t){var e=document.createElement("script");e.type="text/javascript";e.async=!0;e.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)};analytics.SNIPPET_VERSION="3.0.1";
|
!function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","group","track","ready","alias","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t){var e=document.createElement("script");e.type="text/javascript";e.async=!0;e.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)};analytics.SNIPPET_VERSION="3.0.1";
|
||||||
analytics.load(window.config.SegmentDeveloperKey);
|
analytics.load(window.mm_config.SegmentDeveloperKey);
|
||||||
var user = window.UserStore.getCurrentUser(true);
|
var user = window.UserStore.getCurrentUser(true);
|
||||||
if (user) {
|
if (user) {
|
||||||
analytics.identify(user.id, {
|
analytics.identify(user.id, {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
window.setup_home_page({{ .Props }});
|
window.setup_home_page();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="signup-team__container">
|
<div class="signup-team__container">
|
||||||
<img class="signup-team-logo" src="/static/images/logo.png" />
|
<img class="signup-team-logo" src="/static/images/logo.png" />
|
||||||
<h1>{{ .ClientProps.SiteName }}</h1>
|
<h1>{{ .ClientCfg.SiteName }}</h1>
|
||||||
<h4 class="color--light">All team communication in one place, searchable and accessible anywhere</h4>
|
<h4 class="color--light">All team communication in one place, searchable and accessible anywhere</h4>
|
||||||
<div id="signup-team"></div>
|
<div id="signup-team"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<div class="row main">
|
<div class="row main">
|
||||||
<div class="app__content">
|
<div class="app__content">
|
||||||
<div class="welcome-info">
|
<div class="welcome-info">
|
||||||
<h1>Welcome to {{ .ClientProps.SiteName }}!</h1>
|
<h1>Welcome to {{ .ClientCfg.SiteName }}!</h1>
|
||||||
<p>
|
<p>
|
||||||
You do not appear to be part of any teams. Please contact your
|
You do not appear to be part of any teams. Please contact your
|
||||||
administrator to have him send you an invitation to a private team.
|
administrator to have him send you an invitation to a private team.
|
||||||
|
|||||||
186
web/web.go
186
web/web.go
@@ -15,6 +15,7 @@ import (
|
|||||||
"gopkg.in/fsnotify.v1"
|
"gopkg.in/fsnotify.v1"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -31,10 +32,20 @@ func NewHtmlTemplatePage(templateName string, title string) *HtmlTemplatePage {
|
|||||||
|
|
||||||
props := make(map[string]string)
|
props := make(map[string]string)
|
||||||
props["Title"] = title
|
props["Title"] = title
|
||||||
return &HtmlTemplatePage{TemplateName: templateName, Props: props, ClientProps: utils.ClientProperties}
|
return &HtmlTemplatePage{TemplateName: templateName, Props: props, ClientCfg: utils.ClientCfg}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (me *HtmlTemplatePage) Render(c *api.Context, w http.ResponseWriter) {
|
func (me *HtmlTemplatePage) Render(c *api.Context, w http.ResponseWriter) {
|
||||||
|
if me.Team != nil {
|
||||||
|
me.Team.Sanitize()
|
||||||
|
}
|
||||||
|
|
||||||
|
if me.User != nil {
|
||||||
|
me.User.Sanitize(map[string]bool{})
|
||||||
|
}
|
||||||
|
|
||||||
|
me.SessionTokenIndex = c.SessionTokenIndex
|
||||||
|
|
||||||
if err := Templates.ExecuteTemplate(w, me.TemplateName, me); err != nil {
|
if err := Templates.ExecuteTemplate(w, me.TemplateName, me); err != nil {
|
||||||
c.SetUnknownError(me.TemplateName, err.Error())
|
c.SetUnknownError(me.TemplateName, err.Error())
|
||||||
}
|
}
|
||||||
@@ -78,9 +89,9 @@ func InitWeb() {
|
|||||||
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/login", api.AppHandler(login)).Methods("GET")
|
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/login", api.AppHandler(login)).Methods("GET")
|
||||||
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/logout", api.AppHandler(logout)).Methods("GET")
|
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/logout", api.AppHandler(logout)).Methods("GET")
|
||||||
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/reset_password", api.AppHandler(resetPassword)).Methods("GET")
|
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/reset_password", api.AppHandler(resetPassword)).Methods("GET")
|
||||||
mainrouter.Handle("/{team}/login/{service}", api.AppHandler(loginWithOAuth)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
|
mainrouter.Handle("/{team}/login/{service}", api.AppHandler(loginWithOAuth)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
|
||||||
mainrouter.Handle("/{team}/channels/{channelname}", api.UserRequired(getChannel)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
|
mainrouter.Handle("/{team}/channels/{channelname}", api.AppHandler(getChannel)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
|
||||||
mainrouter.Handle("/{team}/signup/{service}", api.AppHandler(signupWithOAuth)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
|
mainrouter.Handle("/{team}/signup/{service}", api.AppHandler(signupWithOAuth)).Methods("GET") // Bug in gorilla.mux prevents us from using regex here.
|
||||||
|
|
||||||
watchAndParseTemplates()
|
watchAndParseTemplates()
|
||||||
}
|
}
|
||||||
@@ -141,6 +152,20 @@ func CheckBrowserCompatability(c *api.Context, r *http.Request) bool {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func getTeamAndUser(c *api.Context) (*model.Team, *model.User) {
|
||||||
|
// if tr := <-api.Srv.Store.Team().Get(c.Session.TeamId); tr.Err != nil {
|
||||||
|
// c.Err = tr.Err
|
||||||
|
// return nil, nil
|
||||||
|
// } else {
|
||||||
|
// if ur := <-api.Srv.Store.User().Get(c.Session.UserId); ur.Err != nil {
|
||||||
|
// c.Err = ur.Err
|
||||||
|
// return nil, nil
|
||||||
|
// } else {
|
||||||
|
// return tr.Data.(*model.Team), ur.Data.(*model.User)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
func root(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
func root(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if !CheckBrowserCompatability(c, r) {
|
if !CheckBrowserCompatability(c, r) {
|
||||||
@@ -151,8 +176,29 @@ func root(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
page := NewHtmlTemplatePage("signup_team", "Signup")
|
page := NewHtmlTemplatePage("signup_team", "Signup")
|
||||||
page.Render(c, w)
|
page.Render(c, w)
|
||||||
} else {
|
} else {
|
||||||
|
teamChan := api.Srv.Store.Team().Get(c.Session.TeamId)
|
||||||
|
userChan := api.Srv.Store.User().Get(c.Session.UserId)
|
||||||
|
|
||||||
|
var team *model.Team
|
||||||
|
if tr := <-teamChan; tr.Err != nil {
|
||||||
|
c.Err = tr.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
team = tr.Data.(*model.Team)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var user *model.User
|
||||||
|
if ur := <-userChan; ur.Err != nil {
|
||||||
|
c.Err = ur.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
user = ur.Data.(*model.User)
|
||||||
|
}
|
||||||
|
|
||||||
page := NewHtmlTemplatePage("home", "Home")
|
page := NewHtmlTemplatePage("home", "Home")
|
||||||
page.Props["TeamURL"] = c.GetTeamURL()
|
page.Team = team
|
||||||
|
page.User = user
|
||||||
page.Render(c, w)
|
page.Render(c, w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,50 +222,19 @@ func login(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
var team *model.Team
|
var team *model.Team
|
||||||
if tResult := <-api.Srv.Store.Team().GetByName(teamName); tResult.Err != nil {
|
if tResult := <-api.Srv.Store.Team().GetByName(teamName); tResult.Err != nil {
|
||||||
l4g.Error("Couldn't find team name=%v, teamURL=%v, err=%v", teamName, c.GetTeamURL(), tResult.Err.Message)
|
l4g.Error("Couldn't find team name=%v, err=%v", teamName, tResult.Err.Message)
|
||||||
http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
|
http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
team = tResult.Data.(*model.Team)
|
team = tResult.Data.(*model.Team)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are already logged into this team then go to home
|
|
||||||
if len(c.Session.UserId) != 0 && c.Session.TeamId == team.Id {
|
|
||||||
page := NewHtmlTemplatePage("home", "Home")
|
|
||||||
page.Props["TeamURL"] = c.GetTeamURL()
|
|
||||||
page.Render(c, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// We still might be able to switch to this team because we've logged in before
|
// We still might be able to switch to this team because we've logged in before
|
||||||
if multiCookie, err := r.Cookie(model.MULTI_SESSION_TOKEN); err == nil {
|
_, session := api.FindMultiSessionForTeamId(r, team.Id)
|
||||||
multiToken := multiCookie.Value
|
if session != nil {
|
||||||
|
w.Header().Set(model.HEADER_TOKEN, session.Token)
|
||||||
if len(multiToken) > 0 {
|
http.Redirect(w, r, c.GetSiteURL()+"/"+team.Name+"/channels/town-square", http.StatusTemporaryRedirect)
|
||||||
tokens := strings.Split(multiToken, " ")
|
return
|
||||||
|
|
||||||
for _, token := range tokens {
|
|
||||||
if sr := <-api.Srv.Store.Session().Get(token); sr.Err == nil {
|
|
||||||
s := sr.Data.(*model.Session)
|
|
||||||
|
|
||||||
if !s.IsExpired() && s.TeamId == team.Id {
|
|
||||||
w.Header().Set(model.HEADER_TOKEN, s.Token)
|
|
||||||
sessionCookie := &http.Cookie{
|
|
||||||
Name: model.SESSION_TOKEN,
|
|
||||||
Value: s.Token,
|
|
||||||
Path: "/",
|
|
||||||
MaxAge: model.SESSION_TIME_WEB_IN_SECS,
|
|
||||||
HttpOnly: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
http.SetCookie(w, sessionCookie)
|
|
||||||
|
|
||||||
http.Redirect(w, r, c.GetSiteURL()+"/"+team.Name+"/channels/town-square", http.StatusTemporaryRedirect)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
page := NewHtmlTemplatePage("login", "Login")
|
page := NewHtmlTemplatePage("login", "Login")
|
||||||
@@ -315,7 +330,7 @@ func signupUserComplete(c *api.Context, w http.ResponseWriter, r *http.Request)
|
|||||||
|
|
||||||
func logout(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
func logout(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
||||||
api.Logout(c, w, r)
|
api.Logout(c, w, r)
|
||||||
http.Redirect(w, r, c.GetTeamURL(), http.StatusFound)
|
http.Redirect(w, r, c.GetTeamURL(), http.StatusTemporaryRedirect)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -324,7 +339,27 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
teamName := params["team"]
|
teamName := params["team"]
|
||||||
|
|
||||||
var team *model.Team
|
var team *model.Team
|
||||||
teamChan := api.Srv.Store.Team().Get(c.Session.TeamId)
|
if result := <-api.Srv.Store.Team().GetByName(teamName); result.Err != nil {
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
team = result.Data.(*model.Team)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are logged into a different team. Lets see if we have another
|
||||||
|
// session in the cookie that will give us access.
|
||||||
|
if c.Session.TeamId != team.Id {
|
||||||
|
index, session := api.FindMultiSessionForTeamId(r, team.Id)
|
||||||
|
if session == nil {
|
||||||
|
// redirect to login
|
||||||
|
http.Redirect(w, r, c.GetSiteURL()+"/"+team.Name+"/?redirect="+url.QueryEscape(r.URL.Path), http.StatusTemporaryRedirect)
|
||||||
|
} else {
|
||||||
|
c.Session = *session
|
||||||
|
c.SessionTokenIndex = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userChan := api.Srv.Store.User().Get(c.Session.UserId)
|
||||||
|
|
||||||
var channelId string
|
var channelId string
|
||||||
if result := <-api.Srv.Store.Channel().CheckPermissionsToByName(c.Session.TeamId, name, c.Session.UserId); result.Err != nil {
|
if result := <-api.Srv.Store.Channel().CheckPermissionsToByName(c.Session.TeamId, name, c.Session.UserId); result.Err != nil {
|
||||||
@@ -334,17 +369,14 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
channelId = result.Data.(string)
|
channelId = result.Data.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tResult := <-teamChan; tResult.Err != nil {
|
var user *model.User
|
||||||
c.Err = tResult.Err
|
if ur := <-userChan; ur.Err != nil {
|
||||||
|
c.Err = ur.Err
|
||||||
|
c.RemoveSessionCookie(w, r)
|
||||||
|
l4g.Error("Error in getting users profile for id=%v forcing logout", c.Session.UserId)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
team = tResult.Data.(*model.Team)
|
user = ur.Data.(*model.User)
|
||||||
}
|
|
||||||
|
|
||||||
if team.Name != teamName {
|
|
||||||
l4g.Error("It appears you are logged into " + team.Name + ", but are trying to access " + teamName)
|
|
||||||
http.Redirect(w, r, c.GetSiteURL()+"/"+team.Name+"/channels/town-square", http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(channelId) == 0 {
|
if len(channelId) == 0 {
|
||||||
@@ -365,15 +397,6 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
channelId = sc.Id
|
channelId = sc.Id
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// lets make sure the user is valid
|
|
||||||
if result := <-api.Srv.Store.User().Get(c.Session.UserId); result.Err != nil {
|
|
||||||
c.Err = result.Err
|
|
||||||
c.RemoveSessionCookie(w, r)
|
|
||||||
l4g.Error("Error in getting users profile for id=%v forcing logout", c.Session.UserId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// We will attempt to auto-join open channels
|
// We will attempt to auto-join open channels
|
||||||
if cr := <-api.Srv.Store.Channel().GetByName(c.Session.TeamId, name); cr.Err != nil {
|
if cr := <-api.Srv.Store.Channel().GetByName(c.Session.TeamId, name); cr.Err != nil {
|
||||||
http.Redirect(w, r, c.GetTeamURL()+"/channels/town-square", http.StatusFound)
|
http.Redirect(w, r, c.GetTeamURL()+"/channels/town-square", http.StatusFound)
|
||||||
@@ -394,7 +417,7 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
page := NewHtmlTemplatePage("channel", "")
|
page := NewHtmlTemplatePage("channel", "")
|
||||||
page.Props["Title"] = name + " - " + team.DisplayName + " " + page.ClientProps["SiteName"]
|
page.Props["Title"] = name + " - " + team.DisplayName + " " + page.ClientCfg["SiteName"]
|
||||||
page.Props["TeamDisplayName"] = team.DisplayName
|
page.Props["TeamDisplayName"] = team.DisplayName
|
||||||
page.Props["TeamName"] = team.Name
|
page.Props["TeamName"] = team.Name
|
||||||
page.Props["TeamType"] = team.Type
|
page.Props["TeamType"] = team.Type
|
||||||
@@ -402,6 +425,8 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
page.Props["ChannelName"] = name
|
page.Props["ChannelName"] = name
|
||||||
page.Props["ChannelId"] = channelId
|
page.Props["ChannelId"] = channelId
|
||||||
page.Props["UserId"] = c.Session.UserId
|
page.Props["UserId"] = c.Session.UserId
|
||||||
|
page.Team = team
|
||||||
|
page.User = user
|
||||||
page.Render(c, w)
|
page.Render(c, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,7 +525,7 @@ func resetPassword(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
page := NewHtmlTemplatePage("password_reset", "")
|
page := NewHtmlTemplatePage("password_reset", "")
|
||||||
page.Props["Title"] = "Reset Password " + page.ClientProps["SiteName"]
|
page.Props["Title"] = "Reset Password " + page.ClientCfg["SiteName"]
|
||||||
page.Props["TeamDisplayName"] = teamDisplayName
|
page.Props["TeamDisplayName"] = teamDisplayName
|
||||||
page.Props["TeamName"] = teamName
|
page.Props["TeamName"] = teamName
|
||||||
page.Props["Hash"] = hash
|
page.Props["Hash"] = hash
|
||||||
@@ -627,7 +652,10 @@ func signupCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
root(c, w, r)
|
page := NewHtmlTemplatePage("home", "Home")
|
||||||
|
page.Team = team
|
||||||
|
page.User = ruser
|
||||||
|
page.Render(c, w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -690,6 +718,11 @@ func loginCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
page := NewHtmlTemplatePage("home", "Home")
|
||||||
|
page.Team = team
|
||||||
|
page.User = user
|
||||||
|
page.Render(c, w)
|
||||||
|
|
||||||
root(c, w, r)
|
root(c, w, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -701,12 +734,33 @@ func adminConsole(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
teamChan := api.Srv.Store.Team().Get(c.Session.TeamId)
|
||||||
|
userChan := api.Srv.Store.User().Get(c.Session.UserId)
|
||||||
|
|
||||||
|
var team *model.Team
|
||||||
|
if tr := <-teamChan; tr.Err != nil {
|
||||||
|
c.Err = tr.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
team = tr.Data.(*model.Team)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var user *model.User
|
||||||
|
if ur := <-userChan; ur.Err != nil {
|
||||||
|
c.Err = ur.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
user = ur.Data.(*model.User)
|
||||||
|
}
|
||||||
|
|
||||||
params := mux.Vars(r)
|
params := mux.Vars(r)
|
||||||
activeTab := params["tab"]
|
activeTab := params["tab"]
|
||||||
teamId := params["team"]
|
teamId := params["team"]
|
||||||
|
|
||||||
page := NewHtmlTemplatePage("admin_console", "Admin Console")
|
page := NewHtmlTemplatePage("admin_console", "Admin Console")
|
||||||
|
page.User = user
|
||||||
|
page.Team = team
|
||||||
page.Props["ActiveTab"] = activeTab
|
page.Props["ActiveTab"] = activeTab
|
||||||
page.Props["TeamId"] = teamId
|
page.Props["TeamId"] = teamId
|
||||||
page.Render(c, w)
|
page.Render(c, w)
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user