PLT-2992 Added the ability to use different themes for each team (#3411)
* Cleaned up user_settings_theme.jsx and import_theme_modal.jsx * Made ImportThemeModal use a callback to return the theme to the user settings modal instead of saving it directly * Moved user theme from model to preferences * Added serverside API to delete preferences TODO update package with client stuff * Changed constants.jsx so that Preferences and ActionTypes can be imported on their own * Updated ThemeProps migration code to properly rename solarized code themes * Fixed warnings thrown by AppDispatcher * Added clientside UI to support team-specific themes * Removed debugging code from test * Fixed setting a user's theme when they haven't set their theme before
Этот коммит содержится в:
коммит произвёл
Christopher Speller
родитель
8e810bc2eb
Коммит
caabfbcdd5
@@ -16,6 +16,7 @@ func InitPreference() {
|
|||||||
|
|
||||||
BaseRoutes.Preferences.Handle("/", ApiUserRequired(getAllPreferences)).Methods("GET")
|
BaseRoutes.Preferences.Handle("/", ApiUserRequired(getAllPreferences)).Methods("GET")
|
||||||
BaseRoutes.Preferences.Handle("/save", ApiUserRequired(savePreferences)).Methods("POST")
|
BaseRoutes.Preferences.Handle("/save", ApiUserRequired(savePreferences)).Methods("POST")
|
||||||
|
BaseRoutes.Preferences.Handle("/delete", ApiUserRequired(deletePreferences)).Methods("POST")
|
||||||
BaseRoutes.Preferences.Handle("/{category:[A-Za-z0-9_]+}", ApiUserRequired(getPreferenceCategory)).Methods("GET")
|
BaseRoutes.Preferences.Handle("/{category:[A-Za-z0-9_]+}", ApiUserRequired(getPreferenceCategory)).Methods("GET")
|
||||||
BaseRoutes.Preferences.Handle("/{category:[A-Za-z0-9_]+}/{name:[A-Za-z0-9_]+}", ApiUserRequired(getPreference)).Methods("GET")
|
BaseRoutes.Preferences.Handle("/{category:[A-Za-z0-9_]+}/{name:[A-Za-z0-9_]+}", ApiUserRequired(getPreference)).Methods("GET")
|
||||||
}
|
}
|
||||||
@@ -81,3 +82,30 @@ func getPreference(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write([]byte(data.ToJson()))
|
w.Write([]byte(data.ToJson()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deletePreferences(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
preferences, err := model.PreferencesFromJson(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = model.NewLocAppError("savePreferences", "api.preference.delete_preferences.decode.app_error", nil, err.Error())
|
||||||
|
c.Err.StatusCode = http.StatusBadRequest
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, preference := range preferences {
|
||||||
|
if c.Session.UserId != preference.UserId {
|
||||||
|
c.Err = model.NewLocAppError("deletePreferences", "api.preference.delete_preferences.user_id.app_error",
|
||||||
|
nil, "session.user_id="+c.Session.UserId+",preference.user_id="+preference.UserId)
|
||||||
|
c.Err.StatusCode = http.StatusForbidden
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, preference := range preferences {
|
||||||
|
if result := <-Srv.Store.Preference().Delete(c.Session.UserId, preference.Category, preference.Name); result.Err != nil {
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReturnStatusOK(w)
|
||||||
|
}
|
||||||
|
|||||||
@@ -161,3 +161,49 @@ func TestGetPreference(t *testing.T) {
|
|||||||
t.Fatal("preference updated incorrectly")
|
t.Fatal("preference updated incorrectly")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeletePreferences(t *testing.T) {
|
||||||
|
th := Setup().InitBasic()
|
||||||
|
Client := th.BasicClient
|
||||||
|
user1 := th.BasicUser
|
||||||
|
|
||||||
|
var originalCount int
|
||||||
|
if result, err := Client.GetAllPreferences(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
originalCount = len(result.Data.(model.Preferences))
|
||||||
|
}
|
||||||
|
|
||||||
|
// save 10 preferences
|
||||||
|
var preferences model.Preferences
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
preference := model.Preference{
|
||||||
|
UserId: user1.Id,
|
||||||
|
Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW,
|
||||||
|
Name: model.NewId(),
|
||||||
|
}
|
||||||
|
preferences = append(preferences, preference)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := Client.SetPreferences(&preferences); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete 10 preferences
|
||||||
|
th.LoginBasic2()
|
||||||
|
|
||||||
|
if _, err := Client.DeletePreferences(&preferences); err == nil {
|
||||||
|
t.Fatal("shouldn't have been able to delete another user's preferences")
|
||||||
|
}
|
||||||
|
|
||||||
|
th.LoginBasic()
|
||||||
|
if _, err := Client.DeletePreferences(&preferences); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result, err := Client.GetAllPreferences(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if data := result.Data.(model.Preferences); len(data) != originalCount {
|
||||||
|
t.Fatal("should've deleted preferences")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
24
i18n/en.json
24
i18n/en.json
@@ -1139,6 +1139,14 @@
|
|||||||
"id": "api.post_get_post_by_id.get.app_error",
|
"id": "api.post_get_post_by_id.get.app_error",
|
||||||
"translation": "Unable to get post"
|
"translation": "Unable to get post"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.preference.delete_preferences.decode.app_error",
|
||||||
|
"translation": "Unable to decode preferences from request"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.preference.delete_preferences.user_id.app_error",
|
||||||
|
"translation": "Unable to delete preferences for other user"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.preference.init.debug",
|
"id": "api.preference.init.debug",
|
||||||
"translation": "Initializing preference api routes"
|
"translation": "Initializing preference api routes"
|
||||||
@@ -3011,6 +3019,10 @@
|
|||||||
"id": "model.preference.is_valid.name.app_error",
|
"id": "model.preference.is_valid.name.app_error",
|
||||||
"translation": "Invalid name"
|
"translation": "Invalid name"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "model.preference.is_valid.theme.app_error",
|
||||||
|
"translation": "Invalid theme"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "model.preference.is_valid.value.app_error",
|
"id": "model.preference.is_valid.value.app_error",
|
||||||
"translation": "Value is too long"
|
"translation": "Value is too long"
|
||||||
@@ -3175,10 +3187,6 @@
|
|||||||
"id": "model.user.is_valid.team_id.app_error",
|
"id": "model.user.is_valid.team_id.app_error",
|
||||||
"translation": "Invalid team id"
|
"translation": "Invalid team id"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "model.user.is_valid.theme.app_error",
|
|
||||||
"translation": "Invalid theme"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "model.user.is_valid.update_at.app_error",
|
"id": "model.user.is_valid.update_at.app_error",
|
||||||
"translation": "Update at must be a valid time"
|
"translation": "Update at must be a valid time"
|
||||||
@@ -3775,6 +3783,10 @@
|
|||||||
"id": "store.sql_post.update.app_error",
|
"id": "store.sql_post.update.app_error",
|
||||||
"translation": "We couldn't update the Post"
|
"translation": "We couldn't update the Post"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "store.sql_preference.delete.app_error",
|
||||||
|
"translation": "We encountered an error while deleting preferences"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "store.sql_preference.delete_unused_features.debug",
|
"id": "store.sql_preference.delete_unused_features.debug",
|
||||||
"translation": "Deleting any unused pre-release features"
|
"translation": "Deleting any unused pre-release features"
|
||||||
@@ -4055,6 +4067,10 @@
|
|||||||
"id": "store.sql_user.get_unread_count.app_error",
|
"id": "store.sql_user.get_unread_count.app_error",
|
||||||
"translation": "We could not get the unread message count for the user"
|
"translation": "We could not get the unread message count for the user"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "store.sql_user.migrate_theme.critical",
|
||||||
|
"translation": "Failed to migrate User.ThemeProps to Preferences table %v"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "store.sql_user.missing_account.const",
|
"id": "store.sql_user.missing_account.const",
|
||||||
"translation": "We couldn't find an existing account matching your email address for this team. This team may require an invite from the team owner to join."
|
"translation": "We couldn't find an existing account matching your email address for this team. This team may require an invite from the team owner to join."
|
||||||
|
|||||||
@@ -1556,6 +1556,16 @@ func (c *Client) GetPreferenceCategory(category string) (*Result, *AppError) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeletePreferences deletes a list of preferences owned by the current user. If successful,
|
||||||
|
// it will return status=ok. Otherwise, an error will be returned.
|
||||||
|
func (c *Client) DeletePreferences(preferences *Preferences) (bool, *AppError) {
|
||||||
|
if r, err := c.DoApiPost("/preferences/delete", preferences.ToJson()); err != nil {
|
||||||
|
return false, err
|
||||||
|
} else {
|
||||||
|
return c.CheckStatusOK(r), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) CreateOutgoingWebhook(hook *OutgoingWebhook) (*Result, *AppError) {
|
func (c *Client) CreateOutgoingWebhook(hook *OutgoingWebhook) (*Result, *AppError) {
|
||||||
if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/create", hook.ToJson()); err != nil {
|
if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/create", hook.ToJson()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ package model
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,6 +19,9 @@ const (
|
|||||||
PREFERENCE_CATEGORY_DISPLAY_SETTINGS = "display_settings"
|
PREFERENCE_CATEGORY_DISPLAY_SETTINGS = "display_settings"
|
||||||
PREFERENCE_NAME_COLLAPSE_SETTING = "collapse_previews"
|
PREFERENCE_NAME_COLLAPSE_SETTING = "collapse_previews"
|
||||||
|
|
||||||
|
PREFERENCE_CATEGORY_THEME = "theme"
|
||||||
|
// the name for theme props is the team id
|
||||||
|
|
||||||
PREFERENCE_CATEGORY_LAST = "last"
|
PREFERENCE_CATEGORY_LAST = "last"
|
||||||
PREFERENCE_NAME_LAST_CHANNEL = "channel"
|
PREFERENCE_NAME_LAST_CHANNEL = "channel"
|
||||||
)
|
)
|
||||||
@@ -57,13 +62,48 @@ func (o *Preference) IsValid() *AppError {
|
|||||||
return NewLocAppError("Preference.IsValid", "model.preference.is_valid.category.app_error", nil, "category="+o.Category)
|
return NewLocAppError("Preference.IsValid", "model.preference.is_valid.category.app_error", nil, "category="+o.Category)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(o.Name) == 0 || len(o.Name) > 32 {
|
if len(o.Name) > 32 {
|
||||||
return NewLocAppError("Preference.IsValid", "model.preference.is_valid.name.app_error", nil, "name="+o.Name)
|
return NewLocAppError("Preference.IsValid", "model.preference.is_valid.name.app_error", nil, "name="+o.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if utf8.RuneCountInString(o.Value) > 128 {
|
if utf8.RuneCountInString(o.Value) > 2000 {
|
||||||
return NewLocAppError("Preference.IsValid", "model.preference.is_valid.value.app_error", nil, "value="+o.Value)
|
return NewLocAppError("Preference.IsValid", "model.preference.is_valid.value.app_error", nil, "value="+o.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if o.Category == PREFERENCE_CATEGORY_THEME {
|
||||||
|
var unused map[string]string
|
||||||
|
if err := json.NewDecoder(strings.NewReader(o.Value)).Decode(&unused); err != nil {
|
||||||
|
return NewLocAppError("Preference.IsValid", "model.preference.is_valid.theme.app_error", nil, "value="+o.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Preference) PreUpdate() {
|
||||||
|
if o.Category == PREFERENCE_CATEGORY_THEME {
|
||||||
|
// decode the value of theme (a map of strings to string) and eliminate any invalid values
|
||||||
|
var props map[string]string
|
||||||
|
if err := json.NewDecoder(strings.NewReader(o.Value)).Decode(&props); err != nil {
|
||||||
|
// just continue, the invalid preference value should get caught by IsValid before saving
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
colorPattern := regexp.MustCompile(`^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$`)
|
||||||
|
|
||||||
|
// blank out any invalid theme values
|
||||||
|
for name, value := range props {
|
||||||
|
if name == "image" || name == "type" || name == "codeTheme" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !colorPattern.MatchString(value) {
|
||||||
|
props[name] = "#ffffff"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if b, err := json.Marshal(props); err == nil {
|
||||||
|
o.Value = string(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -31,7 +32,7 @@ func TestPreferenceIsValid(t *testing.T) {
|
|||||||
|
|
||||||
preference.Category = PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW
|
preference.Category = PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW
|
||||||
if err := preference.IsValid(); err != nil {
|
if err := preference.IsValid(); err != nil {
|
||||||
t.Fatal()
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
preference.Name = strings.Repeat("01234567890", 20)
|
preference.Name = strings.Repeat("01234567890", 20)
|
||||||
@@ -41,16 +42,48 @@ func TestPreferenceIsValid(t *testing.T) {
|
|||||||
|
|
||||||
preference.Name = NewId()
|
preference.Name = NewId()
|
||||||
if err := preference.IsValid(); err != nil {
|
if err := preference.IsValid(); err != nil {
|
||||||
t.Fatal()
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
preference.Value = strings.Repeat("01234567890", 20)
|
preference.Value = strings.Repeat("01234567890", 201)
|
||||||
if err := preference.IsValid(); err == nil {
|
if err := preference.IsValid(); err == nil {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
|
|
||||||
preference.Value = "1234garbage"
|
preference.Value = "1234garbage"
|
||||||
if err := preference.IsValid(); err != nil {
|
if err := preference.IsValid(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
preference.Category = PREFERENCE_CATEGORY_THEME
|
||||||
|
if err := preference.IsValid(); err == nil {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
preference.Value = `{"color": "#ff0000", "color2": "#faf"}`
|
||||||
|
if err := preference.IsValid(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPreferencePreUpdate(t *testing.T) {
|
||||||
|
preference := Preference{
|
||||||
|
Category: PREFERENCE_CATEGORY_THEME,
|
||||||
|
Value: `{"color": "#ff0000", "color2": "#faf", "codeTheme": "github", "invalid": "invalid"}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
preference.PreUpdate()
|
||||||
|
|
||||||
|
var props map[string]string
|
||||||
|
if err := json.NewDecoder(strings.NewReader(preference.Value)).Decode(&props); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if props["color"] != "#ff0000" || props["color2"] != "#faf" || props["codeTheme"] != "github" {
|
||||||
|
t.Fatal("shouldn't have changed valid props")
|
||||||
|
}
|
||||||
|
|
||||||
|
if props["invalid"] == "invalid" {
|
||||||
|
t.Fatal("should have changed invalid prop")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ type User struct {
|
|||||||
AllowMarketing bool `json:"allow_marketing,omitempty"`
|
AllowMarketing bool `json:"allow_marketing,omitempty"`
|
||||||
Props StringMap `json:"props,omitempty"`
|
Props StringMap `json:"props,omitempty"`
|
||||||
NotifyProps StringMap `json:"notify_props,omitempty"`
|
NotifyProps StringMap `json:"notify_props,omitempty"`
|
||||||
ThemeProps StringMap `json:"theme_props,omitempty"`
|
|
||||||
LastPasswordUpdate int64 `json:"last_password_update,omitempty"`
|
LastPasswordUpdate int64 `json:"last_password_update,omitempty"`
|
||||||
LastPictureUpdate int64 `json:"last_picture_update,omitempty"`
|
LastPictureUpdate int64 `json:"last_picture_update,omitempty"`
|
||||||
FailedAttempts int `json:"failed_attempts,omitempty"`
|
FailedAttempts int `json:"failed_attempts,omitempty"`
|
||||||
@@ -106,10 +105,6 @@ func (u *User) IsValid() *AppError {
|
|||||||
return NewLocAppError("User.IsValid", "model.user.is_valid.auth_data_pwd.app_error", nil, "user_id="+u.Id)
|
return NewLocAppError("User.IsValid", "model.user.is_valid.auth_data_pwd.app_error", nil, "user_id="+u.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(u.ThemeProps) > 2000 {
|
|
||||||
return NewLocAppError("User.IsValid", "model.user.is_valid.theme.app_error", nil, "user_id="+u.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,21 +174,6 @@ func (u *User) PreUpdate() {
|
|||||||
}
|
}
|
||||||
u.NotifyProps["mention_keys"] = strings.Join(goodKeys, ",")
|
u.NotifyProps["mention_keys"] = strings.Join(goodKeys, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
if u.ThemeProps != nil {
|
|
||||||
colorPattern := regexp.MustCompile(`^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$`)
|
|
||||||
|
|
||||||
// blank out any invalid theme values
|
|
||||||
for name, value := range u.ThemeProps {
|
|
||||||
if name == "image" || name == "type" || name == "codeTheme" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !colorPattern.MatchString(value) {
|
|
||||||
u.ThemeProps[name] = "#ffffff"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) SetDefaultNotifications() {
|
func (u *User) SetDefaultNotifications() {
|
||||||
@@ -282,7 +262,6 @@ func (u *User) ClearNonProfileFields() {
|
|||||||
u.AllowMarketing = false
|
u.AllowMarketing = false
|
||||||
u.Props = StringMap{}
|
u.Props = StringMap{}
|
||||||
u.NotifyProps = StringMap{}
|
u.NotifyProps = StringMap{}
|
||||||
u.ThemeProps = StringMap{}
|
|
||||||
u.LastPasswordUpdate = 0
|
u.LastPasswordUpdate = 0
|
||||||
u.LastPictureUpdate = 0
|
u.LastPictureUpdate = 0
|
||||||
u.FailedAttempts = 0
|
u.FailedAttempts = 0
|
||||||
|
|||||||
@@ -39,19 +39,6 @@ func TestUserPreSave(t *testing.T) {
|
|||||||
func TestUserPreUpdate(t *testing.T) {
|
func TestUserPreUpdate(t *testing.T) {
|
||||||
user := User{Password: "test"}
|
user := User{Password: "test"}
|
||||||
user.PreUpdate()
|
user.PreUpdate()
|
||||||
|
|
||||||
user.ThemeProps = StringMap{
|
|
||||||
"codeTheme": "github",
|
|
||||||
"awayIndicator": "#cdbd4e",
|
|
||||||
"buttonColor": "invalid",
|
|
||||||
}
|
|
||||||
user.PreUpdate()
|
|
||||||
|
|
||||||
if user.ThemeProps["codeTheme"] != "github" || user.ThemeProps["awayIndicator"] != "#cdbd4e" {
|
|
||||||
t.Fatal("shouldn't have changed valid theme props")
|
|
||||||
} else if user.ThemeProps["buttonColor"] != "#ffffff" {
|
|
||||||
t.Fatal("should've changed invalid theme prop")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUserUpdateMentionKeysFromUsername(t *testing.T) {
|
func TestUserUpdateMentionKeysFromUsername(t *testing.T) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func NewSqlPreferenceStore(sqlStore *SqlStore) PreferenceStore {
|
|||||||
table.ColMap("UserId").SetMaxSize(26)
|
table.ColMap("UserId").SetMaxSize(26)
|
||||||
table.ColMap("Category").SetMaxSize(32)
|
table.ColMap("Category").SetMaxSize(32)
|
||||||
table.ColMap("Name").SetMaxSize(32)
|
table.ColMap("Name").SetMaxSize(32)
|
||||||
table.ColMap("Value").SetMaxSize(128)
|
table.ColMap("Value").SetMaxSize(2000)
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
@@ -100,6 +100,8 @@ func (s SqlPreferenceStore) Save(preferences *model.Preferences) StoreChannel {
|
|||||||
func (s SqlPreferenceStore) save(transaction *gorp.Transaction, preference *model.Preference) StoreResult {
|
func (s SqlPreferenceStore) save(transaction *gorp.Transaction, preference *model.Preference) StoreResult {
|
||||||
result := StoreResult{}
|
result := StoreResult{}
|
||||||
|
|
||||||
|
preference.PreUpdate()
|
||||||
|
|
||||||
if result.Err = preference.IsValid(); result.Err != nil {
|
if result.Err = preference.IsValid(); result.Err != nil {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -304,3 +306,26 @@ func (s SqlPreferenceStore) IsFeatureEnabled(feature, userId string) StoreChanne
|
|||||||
|
|
||||||
return storeChannel
|
return storeChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s SqlPreferenceStore) Delete(userId, category, name string) StoreChannel {
|
||||||
|
storeChannel := make(StoreChannel)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
result := StoreResult{}
|
||||||
|
|
||||||
|
if _, err := s.GetMaster().Exec(
|
||||||
|
`DELETE FROM
|
||||||
|
Preferences
|
||||||
|
WHERE
|
||||||
|
UserId = :UserId
|
||||||
|
AND Category = :Category
|
||||||
|
AND Name = :Name`, map[string]interface{}{"UserId": userId, "Category": category, "Name": name}); err != nil {
|
||||||
|
result.Err = model.NewLocAppError("SqlPreferenceStore.Delete", "store.sql_preference.delete.app_error", nil, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
storeChannel <- result
|
||||||
|
close(storeChannel)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return storeChannel
|
||||||
|
}
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ func TestPreferenceGetAll(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPreferenceDelete(t *testing.T) {
|
func TestPreferenceDeleteByUser(t *testing.T) {
|
||||||
Setup()
|
Setup()
|
||||||
|
|
||||||
userId := model.NewId()
|
userId := model.NewId()
|
||||||
@@ -367,3 +367,28 @@ func TestDeleteUnusedFeatures(t *testing.T) {
|
|||||||
t.Fatalf("Found %d features with value 'true', expected to find at least %d features", val, 2)
|
t.Fatalf("Found %d features with value 'true', expected to find at least %d features", val, 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPreferenceDelete(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
preference := model.Preference{
|
||||||
|
UserId: model.NewId(),
|
||||||
|
Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW,
|
||||||
|
Name: model.NewId(),
|
||||||
|
Value: "value1a",
|
||||||
|
}
|
||||||
|
|
||||||
|
Must(store.Preference().Save(&model.Preferences{preference}))
|
||||||
|
|
||||||
|
if prefs := Must(store.Preference().GetAll(preference.UserId)).(model.Preferences); len([]model.Preference(prefs)) != 1 {
|
||||||
|
t.Fatal("should've returned 1 preference")
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-store.Preference().Delete(preference.UserId, preference.Category, preference.Name); result.Err != nil {
|
||||||
|
t.Fatal(result.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefs := Must(store.Preference().GetAll(preference.UserId)).(model.Preferences); len([]model.Preference(prefs)) != 0 {
|
||||||
|
t.Fatal("should've returned no preferences")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
l4g "github.com/alecthomas/log4go"
|
||||||
"github.com/mattermost/platform/model"
|
"github.com/mattermost/platform/model"
|
||||||
"github.com/mattermost/platform/utils"
|
"github.com/mattermost/platform/utils"
|
||||||
)
|
)
|
||||||
@@ -40,7 +42,6 @@ func NewSqlUserStore(sqlStore *SqlStore) UserStore {
|
|||||||
table.ColMap("Roles").SetMaxSize(64)
|
table.ColMap("Roles").SetMaxSize(64)
|
||||||
table.ColMap("Props").SetMaxSize(4000)
|
table.ColMap("Props").SetMaxSize(4000)
|
||||||
table.ColMap("NotifyProps").SetMaxSize(2000)
|
table.ColMap("NotifyProps").SetMaxSize(2000)
|
||||||
table.ColMap("ThemeProps").SetMaxSize(2000)
|
|
||||||
table.ColMap("Locale").SetMaxSize(5)
|
table.ColMap("Locale").SetMaxSize(5)
|
||||||
table.ColMap("MfaSecret").SetMaxSize(128)
|
table.ColMap("MfaSecret").SetMaxSize(128)
|
||||||
}
|
}
|
||||||
@@ -53,27 +54,66 @@ func (us SqlUserStore) UpgradeSchemaIfNeeded() {
|
|||||||
us.CreateColumnIfNotExists("Users", "Locale", "varchar(5)", "character varying(5)", model.DEFAULT_LOCALE)
|
us.CreateColumnIfNotExists("Users", "Locale", "varchar(5)", "character varying(5)", model.DEFAULT_LOCALE)
|
||||||
|
|
||||||
// ADDED for 3.2 REMOVE for 3.6
|
// ADDED for 3.2 REMOVE for 3.6
|
||||||
var data []*model.User
|
if us.DoesColumnExist("Users", "ThemeProps") {
|
||||||
if _, err := us.GetReplica().Select(&data, "SELECT * FROM Users WHERE ThemeProps LIKE '%solarized%'"); err == nil {
|
params := map[string]interface{}{
|
||||||
for _, user := range data {
|
"Category": model.PREFERENCE_CATEGORY_THEME,
|
||||||
shouldUpdate := false
|
"Name": "",
|
||||||
if user.ThemeProps["codeTheme"] == "solarized_dark" {
|
}
|
||||||
user.ThemeProps["codeTheme"] = "solarized-dark"
|
|
||||||
shouldUpdate = true
|
|
||||||
} else if user.ThemeProps["codeTheme"] == "solarized_light" {
|
|
||||||
user.ThemeProps["codeTheme"] = "solarized-light"
|
|
||||||
shouldUpdate = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if shouldUpdate {
|
transaction, err := us.GetMaster().Begin()
|
||||||
if result := <-us.Update(user, true); result.Err != nil {
|
if err != nil {
|
||||||
return
|
themeMigrationFailed(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// increase size of Value column of Preferences table to match the size of the ThemeProps column
|
||||||
|
if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||||
|
if _, err := transaction.Exec("ALTER TABLE Preferences ALTER COLUMN Value TYPE varchar(2000)"); err != nil {
|
||||||
|
themeMigrationFailed(err)
|
||||||
|
}
|
||||||
|
} else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
|
||||||
|
if _, err := transaction.Exec("ALTER TABLE Preferences MODIFY Value text"); err != nil {
|
||||||
|
themeMigrationFailed(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copy data across
|
||||||
|
if _, err := transaction.Exec(
|
||||||
|
`INSERT INTO
|
||||||
|
Preferences(UserId, Category, Name, Value)
|
||||||
|
SELECT
|
||||||
|
Id, '`+model.PREFERENCE_CATEGORY_THEME+`', '', ThemeProps
|
||||||
|
FROM
|
||||||
|
Users`, params); err != nil {
|
||||||
|
themeMigrationFailed(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete old data
|
||||||
|
if _, err := transaction.Exec("ALTER TABLE Users DROP COLUMN ThemeProps"); err != nil {
|
||||||
|
themeMigrationFailed(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := transaction.Commit(); err != nil {
|
||||||
|
themeMigrationFailed(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// rename solarized_* code themes to solarized-* to match client changes in 3.0
|
||||||
|
var data model.Preferences
|
||||||
|
if _, err := us.GetReplica().Select(&data, "SELECT * FROM Preferences WHERE Category = '"+model.PREFERENCE_CATEGORY_THEME+"' AND Value LIKE '%solarized_%'"); err == nil {
|
||||||
|
for i := range data {
|
||||||
|
data[i].Value = strings.Replace(data[i].Value, "solarized_", "solarized-", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
us.Preference().Save(&data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func themeMigrationFailed(err error) {
|
||||||
|
l4g.Critical(utils.T("store.sql_user.migrate_theme.critical"), err)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
panic(fmt.Sprintf(utils.T("store.sql_user.migrate_theme.critical"), err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
func (us SqlUserStore) CreateIndexesIfNotExists() {
|
func (us SqlUserStore) CreateIndexesIfNotExists() {
|
||||||
us.CreateIndexIfNotExists("idx_users_email", "Users", "Email")
|
us.CreateIndexIfNotExists("idx_users_email", "Users", "Email")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -243,6 +243,7 @@ type PreferenceStore interface {
|
|||||||
Get(userId string, category string, name string) StoreChannel
|
Get(userId string, category string, name string) StoreChannel
|
||||||
GetCategory(userId string, category string) StoreChannel
|
GetCategory(userId string, category string) StoreChannel
|
||||||
GetAll(userId string) StoreChannel
|
GetAll(userId string) StoreChannel
|
||||||
|
Delete(userId, category, name string) StoreChannel
|
||||||
PermanentDeleteByUser(userId string) StoreChannel
|
PermanentDeleteByUser(userId string) StoreChannel
|
||||||
IsFeatureEnabled(feature, userId string) StoreChannel
|
IsFeatureEnabled(feature, userId string) StoreChannel
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
// See License.txt for license information.
|
// See License.txt for license information.
|
||||||
|
|
||||||
import Client from 'utils/web_client.jsx';
|
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
|
||||||
import * as AsyncClient from 'utils/async_client.jsx';
|
import * as AsyncClient from 'utils/async_client.jsx';
|
||||||
|
import Client from 'utils/web_client.jsx';
|
||||||
|
|
||||||
|
import PreferenceStore from 'stores/preference_store.jsx';
|
||||||
import TeamStore from 'stores/team_store.jsx';
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
|
||||||
|
import {ActionTypes, Preferences} from 'utils/constants.jsx';
|
||||||
|
|
||||||
export function switchFromLdapToEmail(email, password, ldapPassword, onSuccess, onError) {
|
export function switchFromLdapToEmail(email, password, ldapPassword, onSuccess, onError) {
|
||||||
Client.ldapToEmail(
|
Client.ldapToEmail(
|
||||||
@@ -28,3 +33,52 @@ export function getMoreDmList() {
|
|||||||
AsyncClient.getProfilesForDirectMessageList();
|
AsyncClient.getProfilesForDirectMessageList();
|
||||||
AsyncClient.getTeamMembers(TeamStore.getCurrentId());
|
AsyncClient.getTeamMembers(TeamStore.getCurrentId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function saveTheme(teamId, theme, onSuccess, onError) {
|
||||||
|
AsyncClient.savePreference(
|
||||||
|
Preferences.CATEGORY_THEME,
|
||||||
|
teamId,
|
||||||
|
JSON.stringify(theme),
|
||||||
|
() => {
|
||||||
|
onThemeSaved(teamId, theme, onSuccess);
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
onError(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onThemeSaved(teamId, theme, onSuccess) {
|
||||||
|
const themePreferences = PreferenceStore.getCategory(Preferences.CATEGORY_THEME);
|
||||||
|
|
||||||
|
if (teamId !== '' && themePreferences.size > 1) {
|
||||||
|
// no extra handling to be done to delete team-specific themes
|
||||||
|
onSuccess();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toDelete = [];
|
||||||
|
|
||||||
|
for (const [name] of themePreferences) {
|
||||||
|
if (name === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
toDelete.push({
|
||||||
|
user_id: UserStore.getCurrentId(),
|
||||||
|
category: Preferences.CATEGORY_THEME,
|
||||||
|
name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're saving a new global theme so delete any team-specific ones
|
||||||
|
AsyncClient.deletePreferences(toDelete);
|
||||||
|
|
||||||
|
// delete them locally before we hear from the server so that the UI flow is smoother
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.DELETED_PREFERENCES,
|
||||||
|
preferences: toDelete
|
||||||
|
});
|
||||||
|
|
||||||
|
onSuccess();
|
||||||
|
}
|
||||||
@@ -92,15 +92,6 @@ export default class LoggedIn extends React.Component {
|
|||||||
id: user.id
|
id: user.id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update CSS classes to match user theme
|
|
||||||
if (user) {
|
|
||||||
if ($.isPlainObject(user.theme_props) && !$.isEmptyObject(user.theme_props)) {
|
|
||||||
Utils.applyTheme(user.theme_props);
|
|
||||||
} else {
|
|
||||||
Utils.applyTheme(Constants.THEMES.default);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onUserChanged() {
|
onUserChanged() {
|
||||||
|
|||||||
@@ -41,19 +41,34 @@ export default class NeedsTeam extends React.Component {
|
|||||||
constructor(params) {
|
constructor(params) {
|
||||||
super(params);
|
super(params);
|
||||||
|
|
||||||
this.onChanged = this.onChanged.bind(this);
|
this.onTeamChanged = this.onTeamChanged.bind(this);
|
||||||
|
this.onPreferencesChanged = this.onPreferencesChanged.bind(this);
|
||||||
|
|
||||||
|
const team = TeamStore.getCurrent();
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
team: TeamStore.getCurrent()
|
team,
|
||||||
|
theme: PreferenceStore.getTheme(team.id)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onChanged() {
|
onTeamChanged() {
|
||||||
|
const team = TeamStore.getCurrent();
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
team: TeamStore.getCurrent()
|
team,
|
||||||
|
theme: PreferenceStore.getTheme(team.id)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onPreferencesChanged(category) {
|
||||||
|
if (!category || category === Preferences.CATEGORY_THEME) {
|
||||||
|
this.setState({
|
||||||
|
theme: PreferenceStore.getTheme(this.state.team.id)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
// Go to tutorial if we are first arriving
|
// Go to tutorial if we are first arriving
|
||||||
const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
|
const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
|
||||||
@@ -63,7 +78,8 @@ export default class NeedsTeam extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
TeamStore.addChangeListener(this.onChanged);
|
TeamStore.addChangeListener(this.onTeamChanged);
|
||||||
|
PreferenceStore.addChangeListener(this.onPreferencesChanged);
|
||||||
|
|
||||||
// Emit view action
|
// Emit view action
|
||||||
GlobalActions.viewLoggedIn();
|
GlobalActions.viewLoggedIn();
|
||||||
@@ -80,10 +96,19 @@ export default class NeedsTeam extends React.Component {
|
|||||||
$(window).on('blur', () => {
|
$(window).on('blur', () => {
|
||||||
window.isActive = false;
|
window.isActive = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Utils.applyTheme(this.state.theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
if (!Utils.areObjectsEqual(prevState.theme, this.state.theme)) {
|
||||||
|
Utils.applyTheme(this.state.theme);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
TeamStore.removeChangeListener(this.onChanged);
|
TeamStore.removeChangeListener(this.onTeamChanged);
|
||||||
|
PreferenceStore.removeChangeListener(this.onPreferencesChanged);
|
||||||
$(window).off('focus');
|
$(window).off('focus');
|
||||||
$(window).off('blur');
|
$(window).off('blur');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export default class SettingItemMax extends React.Component {
|
|||||||
</li>
|
</li>
|
||||||
<li className='setting-list-item'>
|
<li className='setting-list-item'>
|
||||||
<hr/>
|
<hr/>
|
||||||
|
{this.props.submitExtra}
|
||||||
{serverError}
|
{serverError}
|
||||||
{clientError}
|
{clientError}
|
||||||
{submit}
|
{submit}
|
||||||
@@ -113,5 +114,6 @@ SettingItemMax.propTypes = {
|
|||||||
updateSection: React.PropTypes.func,
|
updateSection: React.PropTypes.func,
|
||||||
submit: React.PropTypes.func,
|
submit: React.PropTypes.func,
|
||||||
title: React.PropTypes.node,
|
title: React.PropTypes.node,
|
||||||
width: React.PropTypes.string
|
width: React.PropTypes.string,
|
||||||
|
submitExtra: React.PropTypes.node
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,30 +1,18 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import ModalStore from 'stores/modal_store.jsx';
|
import ModalStore from 'stores/modal_store.jsx';
|
||||||
import UserStore from 'stores/user_store.jsx';
|
|
||||||
import * as Utils from 'utils/utils.jsx';
|
|
||||||
import Client from 'utils/web_client.jsx';
|
|
||||||
import {Modal} from 'react-bootstrap';
|
import {Modal} from 'react-bootstrap';
|
||||||
|
|
||||||
import AppDispatcher from '../../dispatcher/app_dispatcher.jsx';
|
|
||||||
import Constants from 'utils/constants.jsx';
|
import Constants from 'utils/constants.jsx';
|
||||||
|
|
||||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
const holders = defineMessages({
|
|
||||||
submitError: {
|
|
||||||
id: 'user.settings.import_theme.submitError',
|
|
||||||
defaultMessage: 'Invalid format, please try copying and pasting in again.'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const ActionTypes = Constants.ActionTypes;
|
const ActionTypes = Constants.ActionTypes;
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
class ImportThemeModal extends React.Component {
|
export default class ImportThemeModal extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
@@ -33,26 +21,42 @@ class ImportThemeModal extends React.Component {
|
|||||||
this.handleChange = this.handleChange.bind(this);
|
this.handleChange = this.handleChange.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
value: '',
|
||||||
inputError: '',
|
inputError: '',
|
||||||
show: false
|
show: false,
|
||||||
|
callback: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
ModalStore.addModalListener(ActionTypes.TOGGLE_IMPORT_THEME_MODAL, this.updateShow);
|
ModalStore.addModalListener(ActionTypes.TOGGLE_IMPORT_THEME_MODAL, this.updateShow);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
ModalStore.removeModalListener(ActionTypes.TOGGLE_IMPORT_THEME_MODAL, this.updateShow);
|
ModalStore.removeModalListener(ActionTypes.TOGGLE_IMPORT_THEME_MODAL, this.updateShow);
|
||||||
}
|
}
|
||||||
updateShow(show) {
|
|
||||||
this.setState({show});
|
updateShow(show, args) {
|
||||||
|
this.setState({
|
||||||
|
show,
|
||||||
|
callback: args.callback
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit(e) {
|
handleSubmit(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const text = ReactDOM.findDOMNode(this.refs.input).value;
|
const text = this.state.value;
|
||||||
|
|
||||||
if (!this.isInputValid(text)) {
|
if (!this.isInputValid(text)) {
|
||||||
this.setState({inputError: this.props.intl.formatMessage(holders.submitError)});
|
this.setState({
|
||||||
|
inputError: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.import_theme.submitError'
|
||||||
|
defaultMessage='Invalid format, please try copying and pasting in again.'
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,26 +85,13 @@ class ImportThemeModal extends React.Component {
|
|||||||
theme.mentionHighlightLink = '#2f81b7';
|
theme.mentionHighlightLink = '#2f81b7';
|
||||||
theme.codeTheme = 'github';
|
theme.codeTheme = 'github';
|
||||||
|
|
||||||
const user = UserStore.getCurrentUser();
|
this.state.callback(theme);
|
||||||
user.theme_props = theme;
|
this.setState({
|
||||||
|
show: false,
|
||||||
Client.updateUser(user, Constants.UserUpdateEvents.THEME,
|
callback: null
|
||||||
(data) => {
|
});
|
||||||
AppDispatcher.handleServerAction({
|
|
||||||
type: ActionTypes.RECEIVED_ME,
|
|
||||||
me: data
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setState({show: false});
|
|
||||||
Utils.applyTheme(theme);
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
var state = this.getStateFromStores();
|
|
||||||
state.serverError = err;
|
|
||||||
this.setState(state);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isInputValid(text) {
|
isInputValid(text) {
|
||||||
if (text.length === 0) {
|
if (text.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -134,13 +125,25 @@ class ImportThemeModal extends React.Component {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange(e) {
|
handleChange(e) {
|
||||||
if (this.isInputValid(e.target.value)) {
|
const value = e.target.value;
|
||||||
|
this.setState({value});
|
||||||
|
|
||||||
|
if (this.isInputValid(value)) {
|
||||||
this.setState({inputError: null});
|
this.setState({inputError: null});
|
||||||
} else {
|
} else {
|
||||||
this.setState({inputError: this.props.intl.formatMessage(holders.submitError)});
|
this.setState({
|
||||||
|
inputError: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.import_theme.submitError'
|
||||||
|
defaultMessage='Invalid format, please try copying and pasting in again.'
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
@@ -170,9 +173,9 @@ class ImportThemeModal extends React.Component {
|
|||||||
<div className='form-group less'>
|
<div className='form-group less'>
|
||||||
<div className='col-sm-9'>
|
<div className='col-sm-9'>
|
||||||
<input
|
<input
|
||||||
ref='input'
|
|
||||||
type='text'
|
type='text'
|
||||||
className='form-control'
|
className='form-control'
|
||||||
|
value={this.state.value}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
/>
|
/>
|
||||||
<div className='input__help'>
|
<div className='input__help'>
|
||||||
@@ -210,9 +213,3 @@ class ImportThemeModal extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImportThemeModal.propTypes = {
|
|
||||||
intl: intlShape.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default injectIntl(ImportThemeModal);
|
|
||||||
|
|||||||
@@ -8,28 +8,18 @@ import PremadeThemeChooser from './premade_theme_chooser.jsx';
|
|||||||
import SettingItemMin from '../setting_item_min.jsx';
|
import SettingItemMin from '../setting_item_min.jsx';
|
||||||
import SettingItemMax from '../setting_item_max.jsx';
|
import SettingItemMax from '../setting_item_max.jsx';
|
||||||
|
|
||||||
|
import PreferenceStore from 'stores/preference_store.jsx';
|
||||||
|
import TeamStore from 'stores/team_store.jsx';
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
|
|
||||||
import AppDispatcher from '../../dispatcher/app_dispatcher.jsx';
|
import AppDispatcher from '../../dispatcher/app_dispatcher.jsx';
|
||||||
import Client from 'utils/web_client.jsx';
|
import * as UserActions from 'actions/user_actions.jsx';
|
||||||
|
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
import Constants from 'utils/constants.jsx';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
|
import {ActionTypes, Constants, Preferences} from 'utils/constants.jsx';
|
||||||
|
|
||||||
const ActionTypes = Constants.ActionTypes;
|
|
||||||
|
|
||||||
const holders = defineMessages({
|
|
||||||
themeTitle: {
|
|
||||||
id: 'user.settings.display.theme.title',
|
|
||||||
defaultMessage: 'Theme'
|
|
||||||
},
|
|
||||||
themeDescribe: {
|
|
||||||
id: 'user.settings.display.theme.describe',
|
|
||||||
defaultMessage: 'Open to manage your theme'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
@@ -47,6 +37,7 @@ export default class ThemeSetting extends React.Component {
|
|||||||
|
|
||||||
this.originalTheme = Object.assign({}, this.state.theme);
|
this.originalTheme = Object.assign({}, this.state.theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
UserStore.addChangeListener(this.onChange);
|
UserStore.addChangeListener(this.onChange);
|
||||||
|
|
||||||
@@ -54,17 +45,20 @@ export default class ThemeSetting extends React.Component {
|
|||||||
$(ReactDOM.findDOMNode(this.refs[this.state.theme])).addClass('active-border');
|
$(ReactDOM.findDOMNode(this.refs[this.state.theme])).addClass('active-border');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
if (this.props.selected) {
|
if (this.props.selected) {
|
||||||
$('.color-btn').removeClass('active-border');
|
$('.color-btn').removeClass('active-border');
|
||||||
$(ReactDOM.findDOMNode(this.refs[this.state.theme])).addClass('active-border');
|
$(ReactDOM.findDOMNode(this.refs[this.state.theme])).addClass('active-border');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (this.props.selected && !nextProps.selected) {
|
if (this.props.selected && !nextProps.selected) {
|
||||||
this.resetFields();
|
this.resetFields();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
UserStore.removeChangeListener(this.onChange);
|
UserStore.removeChangeListener(this.onChange);
|
||||||
|
|
||||||
@@ -73,27 +67,35 @@ export default class ThemeSetting extends React.Component {
|
|||||||
Utils.applyTheme(state.theme);
|
Utils.applyTheme(state.theme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getStateFromStores() {
|
getStateFromStores() {
|
||||||
const user = UserStore.getCurrentUser();
|
const teamId = TeamStore.getCurrentId();
|
||||||
let theme = null;
|
|
||||||
|
|
||||||
if ($.isPlainObject(user.theme_props) && !$.isEmptyObject(user.theme_props)) {
|
|
||||||
theme = Object.assign({}, user.theme_props);
|
|
||||||
} else {
|
|
||||||
theme = $.extend(true, {}, Constants.THEMES.default);
|
|
||||||
}
|
|
||||||
|
|
||||||
let type = 'premade';
|
|
||||||
if (theme.type === 'custom') {
|
|
||||||
type = 'custom';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const theme = PreferenceStore.getTheme(teamId);
|
||||||
if (!theme.codeTheme) {
|
if (!theme.codeTheme) {
|
||||||
theme.codeTheme = Constants.DEFAULT_CODE_THEME;
|
theme.codeTheme = Constants.DEFAULT_CODE_THEME;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {theme, type};
|
let showAllTeamsCheckbox = false;
|
||||||
|
let applyToAllTeams = true;
|
||||||
|
|
||||||
|
if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.LDAP === 'true') {
|
||||||
|
// show the "apply to all teams" checkbox if the user is on more than one team
|
||||||
|
showAllTeamsCheckbox = Object.keys(TeamStore.getAll()).length > 1;
|
||||||
|
|
||||||
|
// check the "apply to all teams" checkbox by default if the user has any team-specific themes
|
||||||
|
applyToAllTeams = PreferenceStore.getCategory(Preferences.CATEGORY_THEME).size <= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
teamId: TeamStore.getCurrentId(),
|
||||||
|
theme,
|
||||||
|
type: theme.type || 'premade',
|
||||||
|
showAllTeamsCheckbox,
|
||||||
|
applyToAllTeams
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange() {
|
onChange() {
|
||||||
const newState = this.getStateFromStores();
|
const newState = this.getStateFromStores();
|
||||||
|
|
||||||
@@ -103,21 +105,20 @@ export default class ThemeSetting extends React.Component {
|
|||||||
|
|
||||||
this.props.setEnforceFocus(true);
|
this.props.setEnforceFocus(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToTop() {
|
scrollToTop() {
|
||||||
$('.ps-container.modal-body').scrollTop(0);
|
$('.ps-container.modal-body').scrollTop(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
submitTheme(e) {
|
submitTheme(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var user = UserStore.getCurrentUser();
|
|
||||||
user.theme_props = this.state.theme;
|
|
||||||
|
|
||||||
Client.updateUser(user, Constants.UserUpdateEvents.THEME,
|
const teamId = this.state.applyToAllTeams ? '' : this.state.teamId;
|
||||||
(data) => {
|
|
||||||
AppDispatcher.handleServerAction({
|
|
||||||
type: ActionTypes.RECEIVED_ME,
|
|
||||||
me: data
|
|
||||||
});
|
|
||||||
|
|
||||||
|
UserActions.saveTheme(
|
||||||
|
teamId,
|
||||||
|
this.state.theme,
|
||||||
|
() => {
|
||||||
this.props.setRequireConfirm(false);
|
this.props.setRequireConfirm(false);
|
||||||
this.originalTheme = Object.assign({}, this.state.theme);
|
this.originalTheme = Object.assign({}, this.state.theme);
|
||||||
this.scrollToTop();
|
this.scrollToTop();
|
||||||
@@ -130,6 +131,7 @@ export default class ThemeSetting extends React.Component {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTheme(theme) {
|
updateTheme(theme) {
|
||||||
let themeChanged = this.state.theme.length === theme.length;
|
let themeChanged = this.state.theme.length === theme.length;
|
||||||
if (!themeChanged) {
|
if (!themeChanged) {
|
||||||
@@ -148,9 +150,11 @@ export default class ThemeSetting extends React.Component {
|
|||||||
this.setState({theme});
|
this.setState({theme});
|
||||||
Utils.applyTheme(theme);
|
Utils.applyTheme(theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateType(type) {
|
updateType(type) {
|
||||||
this.setState({type});
|
this.setState({type});
|
||||||
}
|
}
|
||||||
|
|
||||||
resetFields() {
|
resetFields() {
|
||||||
const state = this.getStateFromStores();
|
const state = this.getStateFromStores();
|
||||||
state.serverError = null;
|
state.serverError = null;
|
||||||
@@ -161,17 +165,18 @@ export default class ThemeSetting extends React.Component {
|
|||||||
|
|
||||||
this.props.setRequireConfirm(false);
|
this.props.setRequireConfirm(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleImportModal() {
|
handleImportModal() {
|
||||||
AppDispatcher.handleViewAction({
|
AppDispatcher.handleViewAction({
|
||||||
type: ActionTypes.TOGGLE_IMPORT_THEME_MODAL,
|
type: ActionTypes.TOGGLE_IMPORT_THEME_MODAL,
|
||||||
value: true
|
value: true,
|
||||||
|
callback: this.updateTheme
|
||||||
});
|
});
|
||||||
|
|
||||||
this.props.setEnforceFocus(false);
|
this.props.setEnforceFocus(false);
|
||||||
}
|
}
|
||||||
render() {
|
|
||||||
const {formatMessage} = this.props.intl;
|
|
||||||
|
|
||||||
|
render() {
|
||||||
var serverError;
|
var serverError;
|
||||||
if (this.state.serverError) {
|
if (this.state.serverError) {
|
||||||
serverError = this.state.serverError;
|
serverError = this.state.serverError;
|
||||||
@@ -266,9 +271,29 @@ export default class ThemeSetting extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let allTeamsCheckbox = null;
|
||||||
|
if (this.state.showAllTeamsCheckbox) {
|
||||||
|
allTeamsCheckbox = (
|
||||||
|
<div className='checkbox user-settings__submit-checkbox'>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
checked={this.state.applyToAllTeams}
|
||||||
|
onChange={(e) => this.setState({applyToAllTeams: e.target.checked})}
|
||||||
|
/>
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.display.theme.applyToAllTeams'
|
||||||
|
defaultMessage='Apply New Theme to All Teams'
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
themeUI = (
|
themeUI = (
|
||||||
<SettingItemMax
|
<SettingItemMax
|
||||||
inputs={inputs}
|
inputs={inputs}
|
||||||
|
submitExtra={allTeamsCheckbox}
|
||||||
submit={this.submitTheme}
|
submit={this.submitTheme}
|
||||||
server_error={serverError}
|
server_error={serverError}
|
||||||
width='full'
|
width='full'
|
||||||
@@ -281,8 +306,18 @@ export default class ThemeSetting extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
themeUI = (
|
themeUI = (
|
||||||
<SettingItemMin
|
<SettingItemMin
|
||||||
title={formatMessage(holders.themeTitle)}
|
title={
|
||||||
describe={formatMessage(holders.themeDescribe)}
|
<FormattedMessage
|
||||||
|
id='user.settings.display.theme.title'
|
||||||
|
defaultMessage='Theme'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
describe={
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.display.theme.describe'
|
||||||
|
defaultMessage='Open to manage your theme'
|
||||||
|
/>
|
||||||
|
}
|
||||||
updateSection={() => {
|
updateSection={() => {
|
||||||
this.props.updateSection('theme');
|
this.props.updateSection('theme');
|
||||||
}}
|
}}
|
||||||
@@ -295,11 +330,8 @@ export default class ThemeSetting extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ThemeSetting.propTypes = {
|
ThemeSetting.propTypes = {
|
||||||
intl: intlShape.isRequired,
|
|
||||||
selected: React.PropTypes.bool.isRequired,
|
selected: React.PropTypes.bool.isRequired,
|
||||||
updateSection: React.PropTypes.func.isRequired,
|
updateSection: React.PropTypes.func.isRequired,
|
||||||
setRequireConfirm: React.PropTypes.func.isRequired,
|
setRequireConfirm: React.PropTypes.func.isRequired,
|
||||||
setEnforceFocus: React.PropTypes.func.isRequired
|
setEnforceFocus: React.PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default injectIntl(ThemeSetting);
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const PayloadSources = Constants.PayloadSources;
|
|||||||
const AppDispatcher = Object.assign(new Flux.Dispatcher(), {
|
const AppDispatcher = Object.assign(new Flux.Dispatcher(), {
|
||||||
handleServerAction: function performServerAction(action) {
|
handleServerAction: function performServerAction(action) {
|
||||||
if (!action.type) {
|
if (!action.type) {
|
||||||
console.warning('handleServerAction called with undefined action type'); // eslint-disable-line no-console
|
console.warn('handleServerAction called with undefined action type'); // eslint-disable-line no-console
|
||||||
}
|
}
|
||||||
|
|
||||||
var payload = {
|
var payload = {
|
||||||
@@ -21,7 +21,7 @@ const AppDispatcher = Object.assign(new Flux.Dispatcher(), {
|
|||||||
|
|
||||||
handleViewAction: function performViewAction(action) {
|
handleViewAction: function performViewAction(action) {
|
||||||
if (!action.type) {
|
if (!action.type) {
|
||||||
console.warning('handleViewAction called with undefined action type'); // eslint-disable-line no-console
|
console.warn('handleViewAction called with undefined action type'); // eslint-disable-line no-console
|
||||||
}
|
}
|
||||||
|
|
||||||
var payload = {
|
var payload = {
|
||||||
|
|||||||
@@ -475,3 +475,8 @@
|
|||||||
.no-resize {
|
.no-resize {
|
||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-settings__submit-checkbox {
|
||||||
|
padding-top: 0px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -54,6 +54,16 @@ class PreferenceStoreClass extends EventEmitter {
|
|||||||
return parseInt(this.preferences.get(key), 10);
|
return parseInt(this.preferences.get(key), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getObject(category, name, defaultValue = null) {
|
||||||
|
const key = this.getKey(category, name);
|
||||||
|
|
||||||
|
if (!this.preferences.has(key)) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.parse(this.preferences.get(key));
|
||||||
|
}
|
||||||
|
|
||||||
getCategory(category) {
|
getCategory(category) {
|
||||||
const prefix = category + '--';
|
const prefix = category + '--';
|
||||||
|
|
||||||
@@ -78,6 +88,10 @@ class PreferenceStoreClass extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deletePreference(preference) {
|
||||||
|
this.preferences.delete(this.getKey(preference.category, preference.name));
|
||||||
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.preferences.clear();
|
this.preferences.clear();
|
||||||
}
|
}
|
||||||
@@ -94,6 +108,18 @@ class PreferenceStoreClass extends EventEmitter {
|
|||||||
this.removeListener(CHANGE_EVENT, callback);
|
this.removeListener(CHANGE_EVENT, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTheme(teamId) {
|
||||||
|
if (this.preferences.has(this.getKey(Constants.Preferences.CATEGORY_THEME, teamId))) {
|
||||||
|
return this.getObject(Constants.Preferences.CATEGORY_THEME, teamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.preferences.has(this.getKey(Constants.Preferences.CATEGORY_THEME, ''))) {
|
||||||
|
return this.getObject(Constants.Preferences.CATEGORY_THEME, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Constants.THEMES.default;
|
||||||
|
}
|
||||||
|
|
||||||
handleEventPayload(payload) {
|
handleEventPayload(payload) {
|
||||||
const action = payload.action;
|
const action = payload.action;
|
||||||
|
|
||||||
@@ -108,6 +134,12 @@ class PreferenceStoreClass extends EventEmitter {
|
|||||||
this.setPreferencesFromServer(action.preferences);
|
this.setPreferencesFromServer(action.preferences);
|
||||||
this.emitChange();
|
this.emitChange();
|
||||||
break;
|
break;
|
||||||
|
case ActionTypes.DELETED_PREFERENCES:
|
||||||
|
for (const preference of action.preferences) {
|
||||||
|
this.deletePreference(preference);
|
||||||
|
}
|
||||||
|
this.emitChange();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -852,6 +852,29 @@ export function savePreferences(preferences, success, error) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deletePreferences(preferences, success, error) {
|
||||||
|
Client.deletePreferences(
|
||||||
|
preferences,
|
||||||
|
(data) => {
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.DELETED_PREFERENCES,
|
||||||
|
preferences
|
||||||
|
});
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
success(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
dispatchError(err, 'deletePreferences');
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function getSuggestedCommands(command, suggestionId, component) {
|
export function getSuggestedCommands(command, suggestionId, component) {
|
||||||
Client.listCommands(
|
Client.listCommands(
|
||||||
(data) => {
|
(data) => {
|
||||||
|
|||||||
@@ -33,100 +33,125 @@ import mattermostDarkThemeImage from 'images/themes/mattermost_dark.png';
|
|||||||
import mattermostThemeImage from 'images/themes/mattermost.png';
|
import mattermostThemeImage from 'images/themes/mattermost.png';
|
||||||
import windows10ThemeImage from 'images/themes/windows_dark.png';
|
import windows10ThemeImage from 'images/themes/windows_dark.png';
|
||||||
|
|
||||||
export default {
|
export const Preferences = {
|
||||||
ActionTypes: keyMirror({
|
CATEGORY_DIRECT_CHANNEL_SHOW: 'direct_channel_show',
|
||||||
RECEIVED_ERROR: null,
|
CATEGORY_DISPLAY_SETTINGS: 'display_settings',
|
||||||
|
DISPLAY_PREFER_NICKNAME: 'nickname_full_name',
|
||||||
|
DISPLAY_PREFER_FULL_NAME: 'full_name',
|
||||||
|
CATEGORY_ADVANCED_SETTINGS: 'advanced_settings',
|
||||||
|
TUTORIAL_STEP: 'tutorial_step',
|
||||||
|
CHANNEL_DISPLAY_MODE: 'channel_display_mode',
|
||||||
|
CHANNEL_DISPLAY_MODE_CENTERED: 'centered',
|
||||||
|
CHANNEL_DISPLAY_MODE_FULL_SCREEN: 'full',
|
||||||
|
CHANNEL_DISPLAY_MODE_DEFAULT: 'centered',
|
||||||
|
MESSAGE_DISPLAY: 'message_display',
|
||||||
|
MESSAGE_DISPLAY_CLEAN: 'clean',
|
||||||
|
MESSAGE_DISPLAY_COMPACT: 'compact',
|
||||||
|
MESSAGE_DISPLAY_DEFAULT: 'clean',
|
||||||
|
COLLAPSE_DISPLAY: 'collapse_previews',
|
||||||
|
COLLAPSE_DISPLAY_DEFAULT: 'false',
|
||||||
|
USE_MILITARY_TIME: 'use_military_time',
|
||||||
|
CATEGORY_THEME: 'theme'
|
||||||
|
};
|
||||||
|
|
||||||
CLICK_CHANNEL: null,
|
export const ActionTypes = keyMirror({
|
||||||
CREATE_CHANNEL: null,
|
RECEIVED_ERROR: null,
|
||||||
LEAVE_CHANNEL: null,
|
|
||||||
CREATE_POST: null,
|
|
||||||
CREATE_COMMENT: null,
|
|
||||||
POST_DELETED: null,
|
|
||||||
REMOVE_POST: null,
|
|
||||||
|
|
||||||
RECEIVED_CHANNELS: null,
|
CLICK_CHANNEL: null,
|
||||||
RECEIVED_CHANNEL: null,
|
CREATE_CHANNEL: null,
|
||||||
RECEIVED_MORE_CHANNELS: null,
|
LEAVE_CHANNEL: null,
|
||||||
RECEIVED_CHANNEL_EXTRA_INFO: null,
|
CREATE_POST: null,
|
||||||
|
CREATE_COMMENT: null,
|
||||||
|
POST_DELETED: null,
|
||||||
|
REMOVE_POST: null,
|
||||||
|
|
||||||
FOCUS_POST: null,
|
RECEIVED_CHANNELS: null,
|
||||||
RECEIVED_POSTS: null,
|
RECEIVED_CHANNEL: null,
|
||||||
RECEIVED_FOCUSED_POST: null,
|
RECEIVED_MORE_CHANNELS: null,
|
||||||
RECEIVED_POST: null,
|
RECEIVED_CHANNEL_EXTRA_INFO: null,
|
||||||
RECEIVED_EDIT_POST: null,
|
|
||||||
RECEIVED_SEARCH: null,
|
|
||||||
RECEIVED_SEARCH_TERM: null,
|
|
||||||
RECEIVED_POST_SELECTED: null,
|
|
||||||
RECEIVED_MENTION_DATA: null,
|
|
||||||
RECEIVED_ADD_MENTION: null,
|
|
||||||
|
|
||||||
RECEIVED_PROFILES_FOR_DM_LIST: null,
|
FOCUS_POST: null,
|
||||||
RECEIVED_PROFILES: null,
|
RECEIVED_POSTS: null,
|
||||||
RECEIVED_DIRECT_PROFILES: null,
|
RECEIVED_FOCUSED_POST: null,
|
||||||
RECEIVED_ME: null,
|
RECEIVED_POST: null,
|
||||||
RECEIVED_SESSIONS: null,
|
RECEIVED_EDIT_POST: null,
|
||||||
RECEIVED_AUDITS: null,
|
RECEIVED_SEARCH: null,
|
||||||
RECEIVED_TEAMS: null,
|
RECEIVED_SEARCH_TERM: null,
|
||||||
RECEIVED_STATUSES: null,
|
RECEIVED_POST_SELECTED: null,
|
||||||
RECEIVED_PREFERENCE: null,
|
RECEIVED_MENTION_DATA: null,
|
||||||
RECEIVED_PREFERENCES: null,
|
RECEIVED_ADD_MENTION: null,
|
||||||
RECEIVED_FILE_INFO: null,
|
|
||||||
RECEIVED_ANALYTICS: null,
|
|
||||||
|
|
||||||
RECEIVED_INCOMING_WEBHOOKS: null,
|
RECEIVED_PROFILES_FOR_DM_LIST: null,
|
||||||
RECEIVED_INCOMING_WEBHOOK: null,
|
RECEIVED_PROFILES: null,
|
||||||
REMOVED_INCOMING_WEBHOOK: null,
|
RECEIVED_DIRECT_PROFILES: null,
|
||||||
RECEIVED_OUTGOING_WEBHOOKS: null,
|
RECEIVED_ME: null,
|
||||||
RECEIVED_OUTGOING_WEBHOOK: null,
|
RECEIVED_SESSIONS: null,
|
||||||
UPDATED_OUTGOING_WEBHOOK: null,
|
RECEIVED_AUDITS: null,
|
||||||
REMOVED_OUTGOING_WEBHOOK: null,
|
RECEIVED_TEAMS: null,
|
||||||
RECEIVED_COMMANDS: null,
|
RECEIVED_STATUSES: null,
|
||||||
RECEIVED_COMMAND: null,
|
RECEIVED_PREFERENCE: null,
|
||||||
UPDATED_COMMAND: null,
|
RECEIVED_PREFERENCES: null,
|
||||||
REMOVED_COMMAND: null,
|
DELETED_PREFERENCES: null,
|
||||||
|
RECEIVED_FILE_INFO: null,
|
||||||
|
RECEIVED_ANALYTICS: null,
|
||||||
|
|
||||||
RECEIVED_CUSTOM_EMOJIS: null,
|
RECEIVED_INCOMING_WEBHOOKS: null,
|
||||||
RECEIVED_CUSTOM_EMOJI: null,
|
RECEIVED_INCOMING_WEBHOOK: null,
|
||||||
UPDATED_CUSTOM_EMOJI: null,
|
REMOVED_INCOMING_WEBHOOK: null,
|
||||||
REMOVED_CUSTOM_EMOJI: null,
|
RECEIVED_OUTGOING_WEBHOOKS: null,
|
||||||
|
RECEIVED_OUTGOING_WEBHOOK: null,
|
||||||
|
UPDATED_OUTGOING_WEBHOOK: null,
|
||||||
|
REMOVED_OUTGOING_WEBHOOK: null,
|
||||||
|
RECEIVED_COMMANDS: null,
|
||||||
|
RECEIVED_COMMAND: null,
|
||||||
|
UPDATED_COMMAND: null,
|
||||||
|
REMOVED_COMMAND: null,
|
||||||
|
|
||||||
RECEIVED_MSG: null,
|
RECEIVED_CUSTOM_EMOJIS: null,
|
||||||
|
RECEIVED_CUSTOM_EMOJI: null,
|
||||||
|
UPDATED_CUSTOM_EMOJI: null,
|
||||||
|
REMOVED_CUSTOM_EMOJI: null,
|
||||||
|
|
||||||
RECEIVED_MY_TEAM: null,
|
RECEIVED_MSG: null,
|
||||||
CREATED_TEAM: null,
|
|
||||||
|
|
||||||
RECEIVED_CONFIG: null,
|
RECEIVED_MY_TEAM: null,
|
||||||
RECEIVED_LOGS: null,
|
CREATED_TEAM: null,
|
||||||
RECEIVED_SERVER_AUDITS: null,
|
|
||||||
RECEIVED_SERVER_COMPLIANCE_REPORTS: null,
|
|
||||||
RECEIVED_ALL_TEAMS: null,
|
|
||||||
RECEIVED_ALL_TEAM_LISTINGS: null,
|
|
||||||
RECEIVED_TEAM_MEMBERS: null,
|
|
||||||
RECEIVED_MEMBERS_FOR_TEAM: null,
|
|
||||||
|
|
||||||
RECEIVED_LOCALE: null,
|
RECEIVED_CONFIG: null,
|
||||||
|
RECEIVED_LOGS: null,
|
||||||
|
RECEIVED_SERVER_AUDITS: null,
|
||||||
|
RECEIVED_SERVER_COMPLIANCE_REPORTS: null,
|
||||||
|
RECEIVED_ALL_TEAMS: null,
|
||||||
|
RECEIVED_ALL_TEAM_LISTINGS: null,
|
||||||
|
RECEIVED_TEAM_MEMBERS: null,
|
||||||
|
RECEIVED_MEMBERS_FOR_TEAM: null,
|
||||||
|
|
||||||
SHOW_SEARCH: null,
|
RECEIVED_LOCALE: null,
|
||||||
|
|
||||||
USER_TYPING: null,
|
SHOW_SEARCH: null,
|
||||||
|
|
||||||
TOGGLE_IMPORT_THEME_MODAL: null,
|
USER_TYPING: null,
|
||||||
TOGGLE_INVITE_MEMBER_MODAL: null,
|
|
||||||
TOGGLE_LEAVE_TEAM_MODAL: null,
|
|
||||||
TOGGLE_DELETE_POST_MODAL: null,
|
|
||||||
TOGGLE_GET_POST_LINK_MODAL: null,
|
|
||||||
TOGGLE_GET_TEAM_INVITE_LINK_MODAL: null,
|
|
||||||
TOGGLE_REGISTER_APP_MODAL: null,
|
|
||||||
TOGGLE_GET_PUBLIC_LINK_MODAL: null,
|
|
||||||
|
|
||||||
SUGGESTION_PRETEXT_CHANGED: null,
|
TOGGLE_IMPORT_THEME_MODAL: null,
|
||||||
SUGGESTION_RECEIVED_SUGGESTIONS: null,
|
TOGGLE_INVITE_MEMBER_MODAL: null,
|
||||||
SUGGESTION_CLEAR_SUGGESTIONS: null,
|
TOGGLE_LEAVE_TEAM_MODAL: null,
|
||||||
SUGGESTION_COMPLETE_WORD: null,
|
TOGGLE_DELETE_POST_MODAL: null,
|
||||||
SUGGESTION_SELECT_NEXT: null,
|
TOGGLE_GET_POST_LINK_MODAL: null,
|
||||||
SUGGESTION_SELECT_PREVIOUS: null
|
TOGGLE_GET_TEAM_INVITE_LINK_MODAL: null,
|
||||||
}),
|
TOGGLE_REGISTER_APP_MODAL: null,
|
||||||
|
TOGGLE_GET_PUBLIC_LINK_MODAL: null,
|
||||||
|
|
||||||
|
SUGGESTION_PRETEXT_CHANGED: null,
|
||||||
|
SUGGESTION_RECEIVED_SUGGESTIONS: null,
|
||||||
|
SUGGESTION_CLEAR_SUGGESTIONS: null,
|
||||||
|
SUGGESTION_COMPLETE_WORD: null,
|
||||||
|
SUGGESTION_SELECT_NEXT: null,
|
||||||
|
SUGGESTION_SELECT_PREVIOUS: null
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Constants = {
|
||||||
|
Preferences,
|
||||||
|
ActionTypes,
|
||||||
|
|
||||||
PayloadSources: keyMirror({
|
PayloadSources: keyMirror({
|
||||||
SERVER_ACTION: null,
|
SERVER_ACTION: null,
|
||||||
@@ -174,7 +199,6 @@ export default {
|
|||||||
FULLNAME: 'fullname',
|
FULLNAME: 'fullname',
|
||||||
NICKNAME: 'nickname',
|
NICKNAME: 'nickname',
|
||||||
EMAIL: 'email',
|
EMAIL: 'email',
|
||||||
THEME: 'theme',
|
|
||||||
LANGUAGE: 'language'
|
LANGUAGE: 'language'
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -551,25 +575,6 @@ export default {
|
|||||||
Ubuntu: 'font--ubuntu'
|
Ubuntu: 'font--ubuntu'
|
||||||
},
|
},
|
||||||
DEFAULT_FONT: 'Open Sans',
|
DEFAULT_FONT: 'Open Sans',
|
||||||
Preferences: {
|
|
||||||
CATEGORY_DIRECT_CHANNEL_SHOW: 'direct_channel_show',
|
|
||||||
CATEGORY_DISPLAY_SETTINGS: 'display_settings',
|
|
||||||
DISPLAY_PREFER_NICKNAME: 'nickname_full_name',
|
|
||||||
DISPLAY_PREFER_FULL_NAME: 'full_name',
|
|
||||||
CATEGORY_ADVANCED_SETTINGS: 'advanced_settings',
|
|
||||||
TUTORIAL_STEP: 'tutorial_step',
|
|
||||||
CHANNEL_DISPLAY_MODE: 'channel_display_mode',
|
|
||||||
CHANNEL_DISPLAY_MODE_CENTERED: 'centered',
|
|
||||||
CHANNEL_DISPLAY_MODE_FULL_SCREEN: 'full',
|
|
||||||
CHANNEL_DISPLAY_MODE_DEFAULT: 'full',
|
|
||||||
MESSAGE_DISPLAY: 'message_display',
|
|
||||||
MESSAGE_DISPLAY_CLEAN: 'clean',
|
|
||||||
MESSAGE_DISPLAY_COMPACT: 'compact',
|
|
||||||
MESSAGE_DISPLAY_DEFAULT: 'clean',
|
|
||||||
COLLAPSE_DISPLAY: 'collapse_previews',
|
|
||||||
COLLAPSE_DISPLAY_DEFAULT: 'false',
|
|
||||||
USE_MILITARY_TIME: 'use_military_time'
|
|
||||||
},
|
|
||||||
TutorialSteps: {
|
TutorialSteps: {
|
||||||
INTRO_SCREENS: 0,
|
INTRO_SCREENS: 0,
|
||||||
POST_POPOVER: 1,
|
POST_POPOVER: 1,
|
||||||
@@ -779,3 +784,5 @@ export default {
|
|||||||
PERMISSIONS_TEAM_ADMIN: 'team_admin',
|
PERMISSIONS_TEAM_ADMIN: 'team_admin',
|
||||||
PERMISSIONS_SYSTEM_ADMIN: 'system_admin'
|
PERMISSIONS_SYSTEM_ADMIN: 'system_admin'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default Constants;
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user