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
Этот коммит содержится в:
Harrison Healey
2016-07-14 10:08:36 -04:00
коммит произвёл Christopher Speller
родитель 8e810bc2eb
Коммит caabfbcdd5
23 изменённых файлов: 671 добавлений и 273 удалений

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

@@ -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) {
if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/create", hook.ToJson()); err != nil {
return nil, err

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

@@ -6,6 +6,8 @@ package model
import (
"encoding/json"
"io"
"regexp"
"strings"
"unicode/utf8"
)
@@ -17,6 +19,9 @@ const (
PREFERENCE_CATEGORY_DISPLAY_SETTINGS = "display_settings"
PREFERENCE_NAME_COLLAPSE_SETTING = "collapse_previews"
PREFERENCE_CATEGORY_THEME = "theme"
// the name for theme props is the team id
PREFERENCE_CATEGORY_LAST = "last"
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)
}
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)
}
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)
}
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
}
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
import (
"encoding/json"
"strings"
"testing"
)
@@ -31,7 +32,7 @@ func TestPreferenceIsValid(t *testing.T) {
preference.Category = PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW
if err := preference.IsValid(); err != nil {
t.Fatal()
t.Fatal(err)
}
preference.Name = strings.Repeat("01234567890", 20)
@@ -41,16 +42,48 @@ func TestPreferenceIsValid(t *testing.T) {
preference.Name = NewId()
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 {
t.Fatal()
}
preference.Value = "1234garbage"
if err := preference.IsValid(); err != nil {
t.Fatal(err)
}
preference.Category = PREFERENCE_CATEGORY_THEME
if err := preference.IsValid(); err == nil {
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"`
Props StringMap `json:"props,omitempty"`
NotifyProps StringMap `json:"notify_props,omitempty"`
ThemeProps StringMap `json:"theme_props,omitempty"`
LastPasswordUpdate int64 `json:"last_password_update,omitempty"`
LastPictureUpdate int64 `json:"last_picture_update,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)
}
if len(u.ThemeProps) > 2000 {
return NewLocAppError("User.IsValid", "model.user.is_valid.theme.app_error", nil, "user_id="+u.Id)
}
return nil
}
@@ -179,21 +174,6 @@ func (u *User) PreUpdate() {
}
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() {
@@ -282,7 +262,6 @@ func (u *User) ClearNonProfileFields() {
u.AllowMarketing = false
u.Props = StringMap{}
u.NotifyProps = StringMap{}
u.ThemeProps = StringMap{}
u.LastPasswordUpdate = 0
u.LastPictureUpdate = 0
u.FailedAttempts = 0

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

@@ -39,19 +39,6 @@ func TestUserPreSave(t *testing.T) {
func TestUserPreUpdate(t *testing.T) {
user := User{Password: "test"}
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) {