Merge branch 'master' into PLT-25
Этот коммит содержится в:
25
CHANGELOG.md
25
CHANGELOG.md
@@ -11,6 +11,31 @@ The "UNDER DEVELOPMENT" section of the Mattermost changelog appears in the produ
|
|||||||
|
|
||||||
- IE 10 no longer supported since global share of IE 10 fell below 5%
|
- IE 10 no longer supported since global share of IE 10 fell below 5%
|
||||||
|
|
||||||
|
## Release v1.1.1 (Bug Fix Release)
|
||||||
|
|
||||||
|
Released 2015-10-20
|
||||||
|
|
||||||
|
### About Bug Fix Releases
|
||||||
|
|
||||||
|
This is a bug fix release (v1.1.1) and recommended only for users needing a fix to the specific issue listed below. All other users should use the most recent major stable build release (v1.1.0).
|
||||||
|
|
||||||
|
[View more information on Mattermost release numbering](https://github.com/mattermost/platform/blob/master/doc/install/release-numbering.md).
|
||||||
|
|
||||||
|
### Release Purpose
|
||||||
|
|
||||||
|
#### Provide option for upgrading database from Mattermost v0.7 to v1.1
|
||||||
|
|
||||||
|
Upgrading Mattermost v0.7 to Mattermost v1.1 originally required installing Mattermost v1.0 to upgrade from the Mattermost v0.7 database, followed by an install of Mattermost v1.1.
|
||||||
|
|
||||||
|
This was problematic for installing Mattermost with GitLab omnibus since GitLab 8.0 contained Mattermost v0.7 and GitLab 8.1 was to include Mattermost v1.1
|
||||||
|
|
||||||
|
Therefore Mattermost v1.1.1 was created that can upgrade the database in Mattermost v0.7 to Mattermost v1.1 directly.
|
||||||
|
|
||||||
|
Users who configured Mattermost v0.7 within GitLab via the `config.json` file should consult [documentation on upgrading configurations from Mattermost v0.7 to Mattermost v1.1](https://github.com/mattermost/platform/blob/master/doc/install/Upgrade-Guide.md#upgrading-mattermost-v07-to-v11).
|
||||||
|
|
||||||
|
#### Removes 32-char limit on salts
|
||||||
|
|
||||||
|
Mattermost v1.1 introduced a 32-char limit on salts that broke the salt generating in GitLab and this restriction was removed for 1.1.1.
|
||||||
|
|
||||||
## Release v1.1.0
|
## Release v1.1.0
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
sr.Handle("/analytics/{id:[A-Za-z0-9]+}/{name:[A-Za-z0-9_]+}", ApiAppHandler(getAnalytics)).Methods("GET")
|
sr.Handle("/analytics/{id:[A-Za-z0-9]+}/{name:[A-Za-z0-9_]+}", ApiAppHandler(getAnalytics)).Methods("GET")
|
||||||
}
|
}
|
||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,6 +508,8 @@ func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
sc := Srv.Store.Channel().Get(id)
|
sc := Srv.Store.Channel().Get(id)
|
||||||
scm := Srv.Store.Channel().GetMember(id, c.Session.UserId)
|
scm := Srv.Store.Channel().GetMember(id, c.Session.UserId)
|
||||||
uc := Srv.Store.User().Get(c.Session.UserId)
|
uc := Srv.Store.User().Get(c.Session.UserId)
|
||||||
|
ihc := Srv.Store.Webhook().GetIncomingByChannel(id)
|
||||||
|
ohc := Srv.Store.Webhook().GetOutgoingByChannel(id)
|
||||||
|
|
||||||
if cresult := <-sc; cresult.Err != nil {
|
if cresult := <-sc; cresult.Err != nil {
|
||||||
c.Err = cresult.Err
|
c.Err = cresult.Err
|
||||||
@@ -513,10 +520,18 @@ func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
} else if scmresult := <-scm; scmresult.Err != nil {
|
} else if scmresult := <-scm; scmresult.Err != nil {
|
||||||
c.Err = scmresult.Err
|
c.Err = scmresult.Err
|
||||||
return
|
return
|
||||||
|
} else if ihcresult := <-ihc; ihcresult.Err != nil {
|
||||||
|
c.Err = ihcresult.Err
|
||||||
|
return
|
||||||
|
} else if ohcresult := <-ohc; ohcresult.Err != nil {
|
||||||
|
c.Err = ohcresult.Err
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
channel := cresult.Data.(*model.Channel)
|
channel := cresult.Data.(*model.Channel)
|
||||||
user := uresult.Data.(*model.User)
|
user := uresult.Data.(*model.User)
|
||||||
channelMember := scmresult.Data.(model.ChannelMember)
|
channelMember := scmresult.Data.(model.ChannelMember)
|
||||||
|
incomingHooks := ihcresult.Data.([]*model.IncomingWebhook)
|
||||||
|
outgoingHooks := ohcresult.Data.([]*model.OutgoingWebhook)
|
||||||
|
|
||||||
if !c.HasPermissionsToTeam(channel.TeamId, "deleteChannel") {
|
if !c.HasPermissionsToTeam(channel.TeamId, "deleteChannel") {
|
||||||
return
|
return
|
||||||
@@ -540,6 +555,23 @@ func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := model.GetMillis()
|
||||||
|
for _, hook := range incomingHooks {
|
||||||
|
go func() {
|
||||||
|
if result := <-Srv.Store.Webhook().DeleteIncoming(hook.Id, now); result.Err != nil {
|
||||||
|
l4g.Error("Encountered error deleting incoming webhook, id=" + hook.Id)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, hook := range outgoingHooks {
|
||||||
|
go func() {
|
||||||
|
if result := <-Srv.Store.Webhook().DeleteOutgoing(hook.Id, now); result.Err != nil {
|
||||||
|
l4g.Error("Encountered error deleting outgoing webhook, id=" + hook.Id)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
if dresult := <-Srv.Store.Channel().Delete(channel.Id, model.GetMillis()); dresult.Err != nil {
|
if dresult := <-Srv.Store.Channel().Delete(channel.Id, model.GetMillis()); dresult.Err != nil {
|
||||||
c.Err = dresult.Err
|
c.Err = dresult.Err
|
||||||
return
|
return
|
||||||
|
|||||||
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
|
||||||
|
```
|
||||||
@@ -12,5 +12,6 @@
|
|||||||
- This error may appear when attempt to use a self-signed certificate to setup SSL, which is not yet supported by Mattermost. You can resolve this issue by setting up a load balancer like Ngnix. A ticket exists to [add support for self-signed certificates in future](x509: certificate signed by unknown authority).
|
- This error may appear when attempt to use a self-signed certificate to setup SSL, which is not yet supported by Mattermost. You can resolve this issue by setting up a load balancer like Ngnix. A ticket exists to [add support for self-signed certificates in future](x509: certificate signed by unknown authority).
|
||||||
|
|
||||||
##### Lost System Administrator account
|
##### Lost System Administrator account
|
||||||
- If the System Administrator account becomes unavailable, a person leaving the organization for example, you can set a new system admin from the commandline using `./platform -assign_role -team_name="yourteam" -email="you@example.com" -role="system_admin"`
|
- If the System Administrator account becomes unavailable, a person leaving the organization for example, you can set a new system admin from the commandline using `./platform -assign_role -team_name="yourteam" -email="you@example.com" -role="system_admin"`.
|
||||||
|
- After assigning the role the user needs to log out and log back in before the System Administrator role is applied.
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ The following steps can be used to configure Mattermost to use GitLab as a singl
|
|||||||
* _TokenEndpoint_: `https://<your-gitlab-url>/oauth/token`
|
* _TokenEndpoint_: `https://<your-gitlab-url>/oauth/token`
|
||||||
* _UserApiEndpoint_: `https://<your-gitlab-url>/api/v3/user`
|
* _UserApiEndpoint_: `https://<your-gitlab-url>/api/v3/user`
|
||||||
|
|
||||||
Note: Make sure your `HTTPS` or `HTTP` prefix for endpoint URLs matches how your server configuration.
|
Note: Make sure your `HTTPS` or `HTTP` prefix for endpoint URLs matches your server configuration.
|
||||||
|
|
||||||
5. (Optional) If you would like to force all users to sign-up with GitLab only, in the _ServiceSettings_ section of config/config.json please set _DisableEmailSignUp_ to `true`.
|
5. (Optional) If you would like to force all users to sign-up with GitLab only, in the _ServiceSettings_ section of config/config.json please set _DisableEmailSignUp_ to `true`.
|
||||||
|
|
||||||
|
|||||||
@@ -90,3 +90,7 @@ As mentioned above, Mattermost makes it easy to take integrations written for Sl
|
|||||||
|
|
||||||
To see samples and community contributions, please visit <http://mattermost.org/webhooks>.
|
To see samples and community contributions, please visit <http://mattermost.org/webhooks>.
|
||||||
|
|
||||||
|
#### Limitations
|
||||||
|
|
||||||
|
- The `attachments` payload used in Slack is not yet supported
|
||||||
|
- Overriding of usernames does not yet apply to notifications
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ func NewSqlStore() Store {
|
|||||||
}
|
}
|
||||||
|
|
||||||
schemaVersion := sqlStore.GetCurrentSchemaVersion()
|
schemaVersion := sqlStore.GetCurrentSchemaVersion()
|
||||||
|
isSchemaVersion07 := false
|
||||||
|
|
||||||
// If the version is already set then we are potentially in an 'upgrade needed' state
|
// If the version is already set then we are potentially in an 'upgrade needed' state
|
||||||
if schemaVersion != "" {
|
if schemaVersion != "" {
|
||||||
@@ -81,7 +82,6 @@ func NewSqlStore() Store {
|
|||||||
// If we are upgrading from the previous version then print a warning and continue
|
// If we are upgrading from the previous version then print a warning and continue
|
||||||
|
|
||||||
// Special case
|
// Special case
|
||||||
isSchemaVersion07 := false
|
|
||||||
if schemaVersion == "0.7.1" || schemaVersion == "0.7.0" {
|
if schemaVersion == "0.7.1" || schemaVersion == "0.7.0" {
|
||||||
isSchemaVersion07 = true
|
isSchemaVersion07 = true
|
||||||
}
|
}
|
||||||
@@ -140,7 +140,7 @@ func NewSqlStore() Store {
|
|||||||
sqlStore.webhook.(*SqlWebhookStore).CreateIndexesIfNotExists()
|
sqlStore.webhook.(*SqlWebhookStore).CreateIndexesIfNotExists()
|
||||||
sqlStore.preference.(*SqlPreferenceStore).CreateIndexesIfNotExists()
|
sqlStore.preference.(*SqlPreferenceStore).CreateIndexesIfNotExists()
|
||||||
|
|
||||||
if model.IsPreviousVersion(schemaVersion) {
|
if model.IsPreviousVersion(schemaVersion) || isSchemaVersion07 {
|
||||||
sqlStore.system.Update(&model.System{Name: "Version", Value: model.CurrentVersion})
|
sqlStore.system.Update(&model.System{Name: "Version", Value: model.CurrentVersion})
|
||||||
l4g.Warn("The database schema has been upgraded to version " + model.CurrentVersion)
|
l4g.Warn("The database schema has been upgraded to version " + model.CurrentVersion)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -137,6 +137,27 @@ func (s SqlWebhookStore) GetIncomingByUser(userId string) StoreChannel {
|
|||||||
return storeChannel
|
return storeChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s SqlWebhookStore) GetIncomingByChannel(channelId string) StoreChannel {
|
||||||
|
storeChannel := make(StoreChannel)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
result := StoreResult{}
|
||||||
|
|
||||||
|
var webhooks []*model.IncomingWebhook
|
||||||
|
|
||||||
|
if _, err := s.GetReplica().Select(&webhooks, "SELECT * FROM IncomingWebhooks WHERE ChannelId = :ChannelId AND DeleteAt = 0", map[string]interface{}{"ChannelId": channelId}); err != nil {
|
||||||
|
result.Err = model.NewAppError("SqlWebhookStore.GetIncomingByChannel", "We couldn't get the webhooks", "channelId="+channelId+", err="+err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Data = webhooks
|
||||||
|
|
||||||
|
storeChannel <- result
|
||||||
|
close(storeChannel)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return storeChannel
|
||||||
|
}
|
||||||
|
|
||||||
func (s SqlWebhookStore) SaveOutgoing(webhook *model.OutgoingWebhook) StoreChannel {
|
func (s SqlWebhookStore) SaveOutgoing(webhook *model.OutgoingWebhook) StoreChannel {
|
||||||
storeChannel := make(StoreChannel)
|
storeChannel := make(StoreChannel)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -153,6 +154,7 @@ type WebhookStore interface {
|
|||||||
SaveIncoming(webhook *model.IncomingWebhook) StoreChannel
|
SaveIncoming(webhook *model.IncomingWebhook) StoreChannel
|
||||||
GetIncoming(id string) StoreChannel
|
GetIncoming(id string) StoreChannel
|
||||||
GetIncomingByUser(userId string) StoreChannel
|
GetIncomingByUser(userId string) StoreChannel
|
||||||
|
GetIncomingByChannel(channelId string) StoreChannel
|
||||||
DeleteIncoming(webhookId string, time int64) StoreChannel
|
DeleteIncoming(webhookId string, time int64) StoreChannel
|
||||||
SaveOutgoing(webhook *model.OutgoingWebhook) StoreChannel
|
SaveOutgoing(webhook *model.OutgoingWebhook) StoreChannel
|
||||||
GetOutgoing(id string) StoreChannel
|
GetOutgoing(id string) 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(
|
||||||
|
|||||||
@@ -12,19 +12,21 @@ export default class FileUploadOverlay extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={overlayClass}>
|
<div className={overlayClass}>
|
||||||
<div className='overlay__circle'>
|
<div className='overlay__indent'>
|
||||||
<img
|
<div className='overlay__circle'>
|
||||||
className='overlay__files'
|
<img
|
||||||
src='/static/images/filesOverlay.png'
|
className='overlay__files'
|
||||||
alt='Files'
|
src='/static/images/filesOverlay.png'
|
||||||
/>
|
alt='Files'
|
||||||
<span><i className='fa fa-upload'></i>{'Drop a file to upload it.'}</span>
|
/>
|
||||||
<img
|
<span><i className='fa fa-upload'></i>{'Drop a file to upload it.'}</span>
|
||||||
className='overlay__logo'
|
<img
|
||||||
src='/static/images/logoWhite.png'
|
className='overlay__logo'
|
||||||
width='100'
|
src='/static/images/logoWhite.png'
|
||||||
alt='Logo'
|
width='100'
|
||||||
/>
|
alt='Logo'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -120,6 +120,10 @@ export default class Post extends React.Component {
|
|||||||
var parentPost = this.props.parentPost;
|
var parentPost = this.props.parentPost;
|
||||||
var posts = this.props.posts;
|
var posts = this.props.posts;
|
||||||
|
|
||||||
|
if (!post.props) {
|
||||||
|
post.props = {};
|
||||||
|
}
|
||||||
|
|
||||||
var type = 'Post';
|
var type = 'Post';
|
||||||
if (post.root_id && post.root_id.length > 0) {
|
if (post.root_id && post.root_id.length > 0) {
|
||||||
type = 'Comment';
|
type = 'Comment';
|
||||||
@@ -140,7 +144,7 @@ export default class Post extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var currentUserCss = '';
|
var currentUserCss = '';
|
||||||
if (UserStore.getCurrentId() === post.user_id) {
|
if (UserStore.getCurrentId() === post.user_id && !post.props.from_webhook) {
|
||||||
currentUserCss = 'current--user';
|
currentUserCss = 'current--user';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,8 +162,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;
|
||||||
}
|
}
|
||||||
@@ -200,6 +204,7 @@ export default class Post extends React.Component {
|
|||||||
posts={posts}
|
posts={posts}
|
||||||
handleCommentClick={this.handleCommentClick}
|
handleCommentClick={this.handleCommentClick}
|
||||||
retryPost={this.retryPost}
|
retryPost={this.retryPost}
|
||||||
|
resize={this.props.resize}
|
||||||
/>
|
/>
|
||||||
<PostInfo
|
<PostInfo
|
||||||
ref='info'
|
ref='info'
|
||||||
@@ -223,5 +228,6 @@ Post.propTypes = {
|
|||||||
sameUser: React.PropTypes.bool,
|
sameUser: React.PropTypes.bool,
|
||||||
sameRoot: React.PropTypes.bool,
|
sameRoot: React.PropTypes.bool,
|
||||||
hideProfilePic: React.PropTypes.bool,
|
hideProfilePic: React.PropTypes.bool,
|
||||||
isLastComment: React.PropTypes.bool
|
isLastComment: React.PropTypes.bool,
|
||||||
|
resize: React.PropTypes.func
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,8 +13,12 @@ export default class PostBody extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.receivedYoutubeData = false;
|
this.receivedYoutubeData = false;
|
||||||
|
this.isGifLoading = false;
|
||||||
|
|
||||||
this.parseEmojis = this.parseEmojis.bind(this);
|
this.parseEmojis = this.parseEmojis.bind(this);
|
||||||
|
this.createEmbed = this.createEmbed.bind(this);
|
||||||
|
this.createGifEmbed = this.createGifEmbed.bind(this);
|
||||||
|
this.loadGif = this.loadGif.bind(this);
|
||||||
this.createYoutubeEmbed = this.createYoutubeEmbed.bind(this);
|
this.createYoutubeEmbed = this.createYoutubeEmbed.bind(this);
|
||||||
|
|
||||||
const linkData = Utils.extractLinks(this.props.post.message);
|
const linkData = Utils.extractLinks(this.props.post.message);
|
||||||
@@ -46,6 +50,7 @@ export default class PostBody extends React.Component {
|
|||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
this.parseEmojis();
|
this.parseEmojis();
|
||||||
|
this.props.resize();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
@@ -53,6 +58,52 @@ export default class PostBody extends React.Component {
|
|||||||
this.setState({links: linkData.links, message: linkData.text});
|
this.setState({links: linkData.links, message: linkData.text});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createEmbed(link) {
|
||||||
|
let embed = this.createYoutubeEmbed(link);
|
||||||
|
|
||||||
|
if (embed != null) {
|
||||||
|
return embed;
|
||||||
|
}
|
||||||
|
|
||||||
|
embed = this.createGifEmbed(link);
|
||||||
|
|
||||||
|
return embed;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadGif(src) {
|
||||||
|
if (this.isGifLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isGifLoading = true;
|
||||||
|
|
||||||
|
const gif = new Image();
|
||||||
|
gif.src = src;
|
||||||
|
gif.onload = (
|
||||||
|
() => {
|
||||||
|
this.setState({gifLoaded: true});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
createGifEmbed(link) {
|
||||||
|
if (link.substring(link.length - 4) !== '.gif') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.state.gifLoaded) {
|
||||||
|
this.loadGif(link);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
className='gif-div'
|
||||||
|
src={link}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
handleYoutubeTime(link) {
|
handleYoutubeTime(link) {
|
||||||
const timeRegex = /[\\?&]t=([0-9hms]+)/;
|
const timeRegex = /[\\?&]t=([0-9hms]+)/;
|
||||||
|
|
||||||
@@ -119,12 +170,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)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -247,7 +298,7 @@ export default class PostBody extends React.Component {
|
|||||||
|
|
||||||
let embed;
|
let embed;
|
||||||
if (filenames.length === 0 && this.state.links) {
|
if (filenames.length === 0 && this.state.links) {
|
||||||
embed = this.createYoutubeEmbed(this.state.links[0]);
|
embed = this.createEmbed(this.state.links[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let fileAttachmentHolder = '';
|
let fileAttachmentHolder = '';
|
||||||
@@ -287,5 +338,6 @@ PostBody.propTypes = {
|
|||||||
post: React.PropTypes.object.isRequired,
|
post: React.PropTypes.object.isRequired,
|
||||||
parentPost: React.PropTypes.object,
|
parentPost: React.PropTypes.object,
|
||||||
retryPost: React.PropTypes.func.isRequired,
|
retryPost: React.PropTypes.func.isRequired,
|
||||||
handleCommentClick: React.PropTypes.func.isRequired
|
handleCommentClick: React.PropTypes.func.isRequired,
|
||||||
|
resize: React.PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,14 @@ 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);
|
||||||
|
this.updateScroll = this.updateScroll.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 +118,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 +152,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 +162,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 +173,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');
|
||||||
|
|
||||||
@@ -199,10 +206,11 @@ export default class PostList extends React.Component {
|
|||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
|
|
||||||
// there's a new post and
|
// there's a new post and
|
||||||
// it's by the user and not a comment
|
// it's by the user (and not from their webhook) and not a comment
|
||||||
} else if (isNewPost &&
|
} else if (isNewPost &&
|
||||||
userId === firstPost.user_id &&
|
userId === firstPost.user_id &&
|
||||||
!utils.isComment(firstPost)) {
|
!firstPost.props.from_webhook &&
|
||||||
|
!Utils.isComment(firstPost)) {
|
||||||
this.scrollToBottom(true);
|
this.scrollToBottom(true);
|
||||||
|
|
||||||
// the user clicked 'load more messages'
|
// the user clicked 'load more messages'
|
||||||
@@ -231,10 +239,20 @@ export default class PostList extends React.Component {
|
|||||||
this.deactivate();
|
this.deactivate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resize() {
|
updateScroll() {
|
||||||
|
if (!this.scrolled) {
|
||||||
|
this.scrollToBottom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 +298,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 +328,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 +341,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 +388,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 +403,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 +417,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 +425,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 +440,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 +461,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 +483,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 +491,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 +525,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 +534,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 +544,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
|
||||||
@@ -539,10 +557,11 @@ export default class PostList extends React.Component {
|
|||||||
posts={posts}
|
posts={posts}
|
||||||
hideProfilePic={hideProfilePic}
|
hideProfilePic={hideProfilePic}
|
||||||
isLastComment={isLastComment}
|
isLastComment={isLastComment}
|
||||||
|
resize={this.updateScroll}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
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 +577,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 +591,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 +657,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 +667,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 Popover = ReactBootstrap.Popover;
|
||||||
|
|
||||||
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}
|
||||||
|
<Popover
|
||||||
|
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>
|
||||||
|
</Popover>
|
||||||
</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'
|
||||||
|
|||||||
@@ -96,7 +96,14 @@ export default class ManageIncomingHooks extends React.Component {
|
|||||||
const options = [];
|
const options = [];
|
||||||
channels.forEach((channel) => {
|
channels.forEach((channel) => {
|
||||||
if (channel.type !== Constants.DM_CHANNEL) {
|
if (channel.type !== Constants.DM_CHANNEL) {
|
||||||
options.push(<option value={channel.id}>{channel.name}</option>);
|
options.push(
|
||||||
|
<option
|
||||||
|
key={'incoming-hook' + channel.id}
|
||||||
|
value={channel.id}
|
||||||
|
>
|
||||||
|
{channel.display_name}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -108,26 +115,31 @@ export default class ManageIncomingHooks extends React.Component {
|
|||||||
const hooks = [];
|
const hooks = [];
|
||||||
this.state.hooks.forEach((hook) => {
|
this.state.hooks.forEach((hook) => {
|
||||||
const c = ChannelStore.get(hook.channel_id);
|
const c = ChannelStore.get(hook.channel_id);
|
||||||
hooks.push(
|
if (c) {
|
||||||
<div className='font--small'>
|
hooks.push(
|
||||||
<div className='padding-top x2 divider-light'></div>
|
<div
|
||||||
<div className='padding-top x2'>
|
key={hook.id}
|
||||||
<strong>{'URL: '}</strong><span className='word-break--all'>{Utils.getWindowLocationOrigin() + '/hooks/' + hook.id}</span>
|
className='font--small'
|
||||||
|
>
|
||||||
|
<div className='padding-top x2 divider-light'></div>
|
||||||
|
<div className='padding-top x2'>
|
||||||
|
<strong>{'URL: '}</strong><span className='word-break--all'>{Utils.getWindowLocationOrigin() + '/hooks/' + hook.id}</span>
|
||||||
|
</div>
|
||||||
|
<div className='padding-top'>
|
||||||
|
<strong>{'Channel: '}</strong>{c.display_name}
|
||||||
|
</div>
|
||||||
|
<div className='padding-top'>
|
||||||
|
<a
|
||||||
|
className={'text-danger'}
|
||||||
|
href='#'
|
||||||
|
onClick={this.removeHook.bind(this, hook.id)}
|
||||||
|
>
|
||||||
|
{'Remove'}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='padding-top'>
|
);
|
||||||
<strong>{'Channel: '}</strong>{c.name}
|
}
|
||||||
</div>
|
|
||||||
<div className='padding-top'>
|
|
||||||
<a
|
|
||||||
className={'text-danger'}
|
|
||||||
href='#'
|
|
||||||
onClick={this.removeHook.bind(this, hook.id)}
|
|
||||||
>
|
|
||||||
{'Remove'}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let displayHooks;
|
let displayHooks;
|
||||||
|
|||||||
@@ -128,21 +128,42 @@ export default class ManageOutgoingHooks extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const channels = ChannelStore.getAll();
|
const channels = ChannelStore.getAll();
|
||||||
const options = [<option value=''>{'--- Select a channel ---'}</option>];
|
const options = [];
|
||||||
|
options.push(
|
||||||
|
<option
|
||||||
|
key='select-channel'
|
||||||
|
value=''
|
||||||
|
>
|
||||||
|
{'--- Select a channel ---'}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
|
||||||
channels.forEach((channel) => {
|
channels.forEach((channel) => {
|
||||||
if (channel.type === Constants.OPEN_CHANNEL) {
|
if (channel.type === Constants.OPEN_CHANNEL) {
|
||||||
options.push(<option value={channel.id}>{channel.name}</option>);
|
options.push(
|
||||||
|
<option
|
||||||
|
key={'outgoing-hook' + channel.id}
|
||||||
|
value={channel.id}
|
||||||
|
>
|
||||||
|
{channel.display_name}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const hooks = [];
|
const hooks = [];
|
||||||
this.state.hooks.forEach((hook) => {
|
this.state.hooks.forEach((hook) => {
|
||||||
const c = ChannelStore.get(hook.channel_id);
|
const c = ChannelStore.get(hook.channel_id);
|
||||||
|
|
||||||
|
if (!c && hook.channel_id && hook.channel_id.length !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let channelDiv;
|
let channelDiv;
|
||||||
if (c) {
|
if (c) {
|
||||||
channelDiv = (
|
channelDiv = (
|
||||||
<div className='padding-top'>
|
<div className='padding-top'>
|
||||||
<strong>{'Channel: '}</strong>{c.name}
|
<strong>{'Channel: '}</strong>{c.display_name}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -157,7 +178,10 @@ export default class ManageOutgoingHooks extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hooks.push(
|
hooks.push(
|
||||||
<div className='font--small'>
|
<div
|
||||||
|
key={hook.id}
|
||||||
|
className='font--small'
|
||||||
|
>
|
||||||
<div className='padding-top x2 divider-light'></div>
|
<div className='padding-top x2 divider-light'></div>
|
||||||
<div className='padding-top x2'>
|
<div className='padding-top x2'>
|
||||||
<strong>{'URLs: '}</strong><span className='word-break--all'>{hook.callback_urls.join(', ')}</span>
|
<strong>{'URLs: '}</strong><span className='word-break--all'>{hook.callback_urls.join(', ')}</span>
|
||||||
|
|||||||
@@ -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,10 +34,10 @@ 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 key='incoming-hook-ui' />
|
||||||
);
|
);
|
||||||
|
|
||||||
incomingHooksSection = (
|
incomingHooksSection = (
|
||||||
@@ -65,10 +65,10 @@ 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 key='outgoing-hook-ui' />
|
||||||
);
|
);
|
||||||
|
|
||||||
outgoingHooksSection = (
|
outgoingHooksSection = (
|
||||||
|
|||||||
@@ -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.hasOwnProperty('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';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,10 +400,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',
|
||||||
@@ -416,7 +416,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,
|
||||||
@@ -140,7 +139,7 @@ module.exports = {
|
|||||||
sidebarText: '#333333',
|
sidebarText: '#333333',
|
||||||
sidebarUnreadText: '#333333',
|
sidebarUnreadText: '#333333',
|
||||||
sidebarTextHoverBg: '#e6f2fa',
|
sidebarTextHoverBg: '#e6f2fa',
|
||||||
sidebarTextActiveBg: '#e1e1e1',
|
sidebarTextActiveBorder: '#378FD2',
|
||||||
sidebarTextActiveColor: '#111111',
|
sidebarTextActiveColor: '#111111',
|
||||||
sidebarHeaderBg: '#2389d7',
|
sidebarHeaderBg: '#2389d7',
|
||||||
sidebarHeaderTextColor: '#ffffff',
|
sidebarHeaderTextColor: '#ffffff',
|
||||||
@@ -162,7 +161,7 @@ module.exports = {
|
|||||||
sidebarText: '#fff',
|
sidebarText: '#fff',
|
||||||
sidebarUnreadText: '#fff',
|
sidebarUnreadText: '#fff',
|
||||||
sidebarTextHoverBg: '#136197',
|
sidebarTextHoverBg: '#136197',
|
||||||
sidebarTextActiveBg: '#136197',
|
sidebarTextActiveBorder: '#7AB0D6',
|
||||||
sidebarTextActiveColor: '#FFFFFF',
|
sidebarTextActiveColor: '#FFFFFF',
|
||||||
sidebarHeaderBg: '#2f81b7',
|
sidebarHeaderBg: '#2f81b7',
|
||||||
sidebarHeaderTextColor: '#FFFFFF',
|
sidebarHeaderTextColor: '#FFFFFF',
|
||||||
@@ -184,7 +183,7 @@ module.exports = {
|
|||||||
sidebarText: '#fff',
|
sidebarText: '#fff',
|
||||||
sidebarUnreadText: '#fff',
|
sidebarUnreadText: '#fff',
|
||||||
sidebarTextHoverBg: '#4A5664',
|
sidebarTextHoverBg: '#4A5664',
|
||||||
sidebarTextActiveBg: '#39769C',
|
sidebarTextActiveBorder: '#39769C',
|
||||||
sidebarTextActiveColor: '#FFFFFF',
|
sidebarTextActiveColor: '#FFFFFF',
|
||||||
sidebarHeaderBg: '#1B2C3E',
|
sidebarHeaderBg: '#1B2C3E',
|
||||||
sidebarHeaderTextColor: '#FFFFFF',
|
sidebarHeaderTextColor: '#FFFFFF',
|
||||||
@@ -206,7 +205,7 @@ module.exports = {
|
|||||||
sidebarText: '#fff',
|
sidebarText: '#fff',
|
||||||
sidebarUnreadText: '#fff',
|
sidebarUnreadText: '#fff',
|
||||||
sidebarTextHoverBg: '#302e30',
|
sidebarTextHoverBg: '#302e30',
|
||||||
sidebarTextActiveBg: '#484748',
|
sidebarTextActiveBorder: '#196CAF',
|
||||||
sidebarTextActiveColor: '#FFFFFF',
|
sidebarTextActiveColor: '#FFFFFF',
|
||||||
sidebarHeaderBg: '#1f1f1f',
|
sidebarHeaderBg: '#1f1f1f',
|
||||||
sidebarHeaderTextColor: '#FFFFFF',
|
sidebarHeaderTextColor: '#FFFFFF',
|
||||||
@@ -249,8 +248,8 @@ module.exports = {
|
|||||||
uiName: 'Sidebar Text Hover BG'
|
uiName: 'Sidebar Text Hover BG'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'sidebarTextActiveBg',
|
id: 'sidebarTextActiveBorder',
|
||||||
uiName: 'Sidebar Text Active BG'
|
uiName: 'Sidebar Text Active Border'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'sidebarTextActiveColor',
|
id: 'sidebarTextActiveColor',
|
||||||
|
|||||||
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Ссылка в новой задаче
Block a user