diff options
55 files changed, 535 insertions, 359 deletions
diff --git a/api/command.go b/api/command.go index a8573cdcc..bebe6629c 100644 --- a/api/command.go +++ b/api/command.go @@ -4,6 +4,7 @@ package api import ( + "crypto/tls" "fmt" "io/ioutil" "net/http" @@ -52,6 +53,8 @@ func InitCommand(r *mux.Router) { sr.Handle("/test", ApiAppHandler(testCommand)).Methods("POST") sr.Handle("/test", ApiAppHandler(testCommand)).Methods("GET") + sr.Handle("/test_e", ApiAppHandler(testEphemeralCommand)).Methods("POST") + sr.Handle("/test_e", ApiAppHandler(testEphemeralCommand)).Methods("GET") } func listCommands(c *Context, w http.ResponseWriter, r *http.Request) { @@ -107,9 +110,8 @@ func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) { provider := GetCommandProvider(trigger) if provider != nil { - response := provider.DoCommand(c, channelId, message) - handleResponse(c, w, response, channelId) + handleResponse(c, w, response, channelId, provider.GetCommand(c)) return } else { chanChan := Srv.Store.Channel().Get(channelId) @@ -172,7 +174,11 @@ func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) { method = "GET" } - client := &http.Client{} + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: *utils.Cfg.ServiceSettings.EnableInsecureOutgoingConnections}, + } + client := &http.Client{Transport: tr} + req, _ := http.NewRequest(method, cmd.URL, strings.NewReader(p.Encode())) req.Header.Set("Accept", "application/json") if cmd.Method == model.COMMAND_METHOD_POST { @@ -187,7 +193,7 @@ func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) { if response == nil { c.Err = model.NewLocAppError("command", "api.command.execute_command.failed_empty.app_error", map[string]interface{}{"Trigger": trigger}, "") } else { - handleResponse(c, w, response, channelId) + handleResponse(c, w, response, channelId, cmd) } } else { body, _ := ioutil.ReadAll(resp.Body) @@ -205,21 +211,41 @@ func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = model.NewLocAppError("command", "api.command.execute_command.not_found.app_error", map[string]interface{}{"Trigger": trigger}, "") } -func handleResponse(c *Context, w http.ResponseWriter, response *model.CommandResponse, channelId string) { +func handleResponse(c *Context, w http.ResponseWriter, response *model.CommandResponse, channelId string, cmd *model.Command) { + + post := &model.Post{} + post.ChannelId = channelId + post.AddProp("from_webhook", "true") + + if utils.Cfg.ServiceSettings.EnablePostUsernameOverride { + if len(cmd.Username) != 0 { + post.AddProp("override_username", cmd.Username) + } else { + post.AddProp("override_username", model.DEFAULT_WEBHOOK_USERNAME) + } + } + + if utils.Cfg.ServiceSettings.EnablePostIconOverride { + if len(cmd.IconURL) != 0 { + post.AddProp("override_icon_url", cmd.IconURL) + } else { + post.AddProp("override_icon_url", model.DEFAULT_WEBHOOK_ICON) + } + } + if response.ResponseType == model.COMMAND_RESPONSE_TYPE_IN_CHANNEL { - post := &model.Post{} - post.ChannelId = channelId post.Message = response.Text if _, err := CreatePost(c, post, true); err != nil { c.Err = model.NewLocAppError("command", "api.command.execute_command.save.app_error", nil, "") } } else if response.ResponseType == model.COMMAND_RESPONSE_TYPE_EPHEMERAL { - // post := &model.Post{} - // post.ChannelId = channelId - // post.Message = "TODO_EPHEMERAL: " + response.Text - // if _, err := CreatePost(c, post, true); err != nil { - // c.Err = model.NewLocAppError("command", "api.command.execute_command.save.app_error", nil, "") - // } + post.Message = response.Text + post.CreateAt = model.GetMillis() + SendEphemeralPost( + c.Session.TeamId, + c.Session.UserId, + post, + ) } w.Write([]byte(response.ToJson())) @@ -399,3 +425,23 @@ func testCommand(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(rc.ToJson())) } + +func testEphemeralCommand(c *Context, w http.ResponseWriter, r *http.Request) { + r.ParseForm() + + msg := "" + if r.Method == "POST" { + msg = msg + "\ntoken=" + r.FormValue("token") + msg = msg + "\nteam_domain=" + r.FormValue("team_domain") + } else { + body, _ := ioutil.ReadAll(r.Body) + msg = string(body) + } + + rc := &model.CommandResponse{ + Text: "test command response " + msg, + ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, + } + + w.Write([]byte(rc.ToJson())) +} diff --git a/api/post.go b/api/post.go index c17da262f..fadabd66e 100644 --- a/api/post.go +++ b/api/post.go @@ -4,6 +4,7 @@ package api import ( + "crypto/tls" "fmt" l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" @@ -401,7 +402,10 @@ func handleWebhookEventsAndForget(c *Context, post *model.Post, team *model.Team p.Set("text", post.Message) p.Set("trigger_word", firstWord) - client := &http.Client{} + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: *utils.Cfg.ServiceSettings.EnableInsecureOutgoingConnections}, + } + client := &http.Client{Transport: tr} for _, url := range hook.CallbackURLs { go func(url string) { @@ -682,7 +686,10 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel * msg.Message = senderName + userLocale("api.post.send_notifications_and_forget.push_mention") + channelName } - httpClient := http.Client{} + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: *utils.Cfg.ServiceSettings.EnableInsecureOutgoingConnections}, + } + httpClient := &http.Client{Transport: tr} request, _ := http.NewRequest("POST", *utils.Cfg.EmailSettings.PushNotificationServer+"/api/v1/send_push", strings.NewReader(msg.ToJson())) l4g.Debug(utils.T("api.post.send_notifications_and_forget.push_notification.debug"), msg.DeviceId, msg.Message) diff --git a/api/slackimport.go b/api/slackimport.go index 6497ac261..5ca209c5c 100644 --- a/api/slackimport.go +++ b/api/slackimport.go @@ -75,7 +75,10 @@ func SlackParseUsers(data io.Reader) []SlackUser { var users []SlackUser if err := decoder.Decode(&users); err != nil { - return make([]SlackUser, 0) + // This actually returns errors that are ignored. + // In this case it is erroring because of a null that Slack + // introduced. So we just return the users here. + return users } return users } diff --git a/api/user.go b/api/user.go index 9926f3ff3..507c83d28 100644 --- a/api/user.go +++ b/api/user.go @@ -5,6 +5,7 @@ package api import ( "bytes" + "crypto/tls" b64 "encoding/base64" "fmt" l4g "github.com/alecthomas/log4go" @@ -1960,7 +1961,10 @@ func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser p.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE) p.Set("redirect_uri", redirectUri) - client := &http.Client{} + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: *utils.Cfg.ServiceSettings.EnableInsecureOutgoingConnections}, + } + client := &http.Client{Transport: tr} req, _ := http.NewRequest("POST", sso.TokenEndpoint, strings.NewReader(p.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") diff --git a/api/webhook.go b/api/webhook.go index 3906d09be..c0f8ea506 100644 --- a/api/webhook.go +++ b/api/webhook.go @@ -238,7 +238,7 @@ func getOutgoingHooks(c *Context, w http.ResponseWriter, r *http.Request) { } func deleteOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.ServiceSettings.EnableIncomingWebhooks { + if !utils.Cfg.ServiceSettings.EnableOutgoingWebhooks { c.Err = model.NewLocAppError("deleteOutgoingHook", "api.webhook.delete_outgoing.disabled.app_error", nil, "") c.Err.StatusCode = http.StatusNotImplemented return diff --git a/config/config.json b/config/config.json index 5ed05fecd..2795546f8 100644 --- a/config/config.json +++ b/config/config.json @@ -14,6 +14,7 @@ "EnableTesting": false, "EnableDeveloper": false, "EnableSecurityFixAlert": true, + "EnableInsecureOutgoingConnections": false, "SessionLengthWebInDays": 30, "SessionLengthMobileInDays": 30, "SessionLengthSSOInDays": 30, @@ -112,4 +113,4 @@ "TokenEndpoint": "", "UserApiEndpoint": "" } -}
\ No newline at end of file +} diff --git a/docker/dev/config_docker.json b/docker/dev/config_docker.json index e831bbb3a..6a1290189 100644 --- a/docker/dev/config_docker.json +++ b/docker/dev/config_docker.json @@ -14,6 +14,7 @@ "EnableTesting": false, "EnableDeveloper": false, "EnableSecurityFixAlert": true, + "EnableInsecureOutgoingConnections": false, "SessionLengthWebInDays" : 30, "SessionLengthMobileInDays" : 30, "SessionLengthSSOInDays" : 30, diff --git a/docker/local/config_docker.json b/docker/local/config_docker.json index e831bbb3a..6a1290189 100644 --- a/docker/local/config_docker.json +++ b/docker/local/config_docker.json @@ -14,6 +14,7 @@ "EnableTesting": false, "EnableDeveloper": false, "EnableSecurityFixAlert": true, + "EnableInsecureOutgoingConnections": false, "SessionLengthWebInDays" : 30, "SessionLengthMobileInDays" : 30, "SessionLengthSSOInDays" : 30, diff --git a/i18n/es.json b/i18n/es.json index 9512bd929..6f9c7499b 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -245,7 +245,7 @@ }, { "id": "api.command.admin_only.app_error", - "translation": "Las ingtegraciones solo pueden ser utilizadas por adminitradores." + "translation": "Las integraciones han sido limitadas a los adminitradores." }, { "id": "api.command.delete.app_error", diff --git a/model/config.go b/model/config.go index acb525abf..aa3dd3586 100644 --- a/model/config.go +++ b/model/config.go @@ -24,26 +24,27 @@ const ( ) type ServiceSettings struct { - ListenAddress string - MaximumLoginAttempts int - SegmentDeveloperKey string - GoogleDeveloperKey string - EnableOAuthServiceProvider bool - EnableIncomingWebhooks bool - EnableOutgoingWebhooks bool - EnableCommands *bool - EnableOnlyAdminIntegrations *bool - EnablePostUsernameOverride bool - EnablePostIconOverride bool - EnableTesting bool - EnableDeveloper *bool - EnableSecurityFixAlert *bool - SessionLengthWebInDays *int - SessionLengthMobileInDays *int - SessionLengthSSOInDays *int - SessionCacheInMinutes *int - WebsocketSecurePort *int - WebsocketPort *int + ListenAddress string + MaximumLoginAttempts int + SegmentDeveloperKey string + GoogleDeveloperKey string + EnableOAuthServiceProvider bool + EnableIncomingWebhooks bool + EnableOutgoingWebhooks bool + EnableCommands *bool + EnableOnlyAdminIntegrations *bool + EnablePostUsernameOverride bool + EnablePostIconOverride bool + EnableTesting bool + EnableDeveloper *bool + EnableSecurityFixAlert *bool + EnableInsecureOutgoingConnections *bool + SessionLengthWebInDays *int + SessionLengthMobileInDays *int + SessionLengthSSOInDays *int + SessionCacheInMinutes *int + WebsocketSecurePort *int + WebsocketPort *int } type SSOSettings struct { @@ -164,7 +165,7 @@ type LdapSettings struct { UsernameAttribute *string IdAttribute *string - // Advansed + // Advanced QueryTimeout *int } @@ -252,6 +253,11 @@ func (o *Config) SetDefaults() { *o.ServiceSettings.EnableSecurityFixAlert = true } + if o.ServiceSettings.EnableInsecureOutgoingConnections == nil { + o.ServiceSettings.EnableInsecureOutgoingConnections = new(bool) + *o.ServiceSettings.EnableInsecureOutgoingConnections = false + } + if o.TeamSettings.RestrictTeamNames == nil { o.TeamSettings.RestrictTeamNames = new(bool) *o.TeamSettings.RestrictTeamNames = true diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx index 2cc68d1ed..f232d4633 100644 --- a/web/react/components/admin_console/service_settings.jsx +++ b/web/react/components/admin_console/service_settings.jsx @@ -75,6 +75,7 @@ class ServiceSettings extends React.Component { config.ServiceSettings.EnableTesting = ReactDOM.findDOMNode(this.refs.EnableTesting).checked; config.ServiceSettings.EnableDeveloper = ReactDOM.findDOMNode(this.refs.EnableDeveloper).checked; config.ServiceSettings.EnableSecurityFixAlert = ReactDOM.findDOMNode(this.refs.EnableSecurityFixAlert).checked; + config.ServiceSettings.EnableInsecureOutgoingConnections = ReactDOM.findDOMNode(this.refs.EnableInsecureOutgoingConnections).checked; config.ServiceSettings.EnableCommands = ReactDOM.findDOMNode(this.refs.EnableCommands).checked; config.ServiceSettings.EnableOnlyAdminIntegrations = ReactDOM.findDOMNode(this.refs.EnableOnlyAdminIntegrations).checked; @@ -720,6 +721,53 @@ class ServiceSettings extends React.Component { <div className='form-group'> <label className='control-label col-sm-4' + htmlFor='EnableInsecureOutgoingConnections' + > + <FormattedMessage + id='admin.service.insecureTlsTitle' + defaultMessage='Enable Insecure Outgoing Connections: ' + /> + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='EnableInsecureOutgoingConnections' + value='true' + ref='EnableInsecureOutgoingConnections' + defaultChecked={this.props.config.ServiceSettings.EnableInsecureOutgoingConnections} + onChange={this.handleChange} + /> + <FormattedMessage + id='admin.service.true' + defaultMessage='true' + /> + </label> + <label className='radio-inline'> + <input + type='radio' + name='EnableInsecureOutgoingConnections' + value='false' + defaultChecked={!this.props.config.ServiceSettings.EnableInsecureOutgoingConnections} + onChange={this.handleChange} + /> + <FormattedMessage + id='admin.service.false' + defaultMessage='false' + /> + </label> + <p className='help-text'> + <FormattedMessage + id='admin.service.insecureTlsDesc' + defaultMessage='When true, any outgoing HTTPS requests will accept unverified, self-signed certificates. For example, outgoing webhooks to a server with a self-signed TLS certificate, using any domain, will be allowed. Note that this makes these connections susceptible to man-in-the-middle attacks.' + /> + </p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' htmlFor='SessionLengthWebInDays' > <FormattedMessage @@ -896,4 +944,4 @@ ServiceSettings.propTypes = { config: React.PropTypes.object }; -export default injectIntl(ServiceSettings);
\ No newline at end of file +export default injectIntl(ServiceSettings); diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx index 8fc3cd63d..a827cbcc6 100644 --- a/web/react/components/channel_header.jsx +++ b/web/react/components/channel_header.jsx @@ -114,7 +114,7 @@ export default class ChannelHeader extends React.Component { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_SEARCH_TERM, + type: ActionTypes.RECEIVED_SEARCH_TERM, term: terms, do_search: true, is_mention_search: true diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index 709485991..55dd8276c 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -152,7 +152,7 @@ class CreateComment extends React.Component { ChannelStore.setChannelMember(member); AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POST, + type: ActionTypes.RECEIVED_POST, post: data }); }.bind(this), @@ -216,7 +216,7 @@ class CreateComment extends React.Component { } AppDispatcher.handleViewAction({ - type: ActionTypes.RECIEVED_EDIT_POST, + type: ActionTypes.RECEIVED_EDIT_POST, refocusId: '#reply_textbox', title: this.props.intl.formatMessage(holders.commentTitle), message: lastPost.message, diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index ecabdaee6..b9fbf09b5 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -364,7 +364,7 @@ class CreatePost extends React.Component { var type = (lastPost.root_id && lastPost.root_id.length > 0) ? formatMessage(holders.comment) : formatMessage(holders.post); AppDispatcher.handleViewAction({ - type: ActionTypes.RECIEVED_EDIT_POST, + type: ActionTypes.RECEIVED_EDIT_POST, refocusId: '#post_textbox', title: type, message: lastPost.message, diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx index 9d7dcb3e5..65ffa96a1 100644 --- a/web/react/components/delete_post_modal.jsx +++ b/web/react/components/delete_post_modal.jsx @@ -62,12 +62,12 @@ export default class DeletePostModal extends React.Component { var selectedPost = selectedList.posts[selectedList.order[0]]; if ((selectedPost.id === this.state.post.id && !this.state.root_id) || selectedPost.root_id === this.state.post.id) { AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_SEARCH, + type: ActionTypes.RECEIVED_SEARCH, results: null }); AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POST_SELECTED, + type: ActionTypes.RECEIVED_POST_SELECTED, results: null }); } else if (selectedPost.id === this.state.post.id && this.state.root_id) { @@ -76,12 +76,12 @@ export default class DeletePostModal extends React.Component { delete selectedList.posts[selectedPost.id]; AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POST_SELECTED, + type: ActionTypes.RECEIVED_POST_SELECTED, post_list: selectedList }); AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_SEARCH, + type: ActionTypes.RECEIVED_SEARCH, results: null }); } diff --git a/web/react/components/edit_channel_header_modal.jsx b/web/react/components/edit_channel_header_modal.jsx index 1066d123e..f6865fadd 100644 --- a/web/react/components/edit_channel_header_modal.jsx +++ b/web/react/components/edit_channel_header_modal.jsx @@ -68,7 +68,7 @@ class EditChannelHeaderModal extends React.Component { this.onHide(); AppDispatcher.handleServerAction({ - type: Constants.ActionTypes.RECIEVED_CHANNEL, + type: Constants.ActionTypes.RECEIVED_CHANNEL, channel }); }, diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx index 8005678a2..e6a9fbd25 100644 --- a/web/react/components/navbar.jsx +++ b/web/react/components/navbar.jsx @@ -82,12 +82,12 @@ export default class Navbar extends React.Component { var windowWidth = $(window).outerWidth(); if (windowWidth <= 768) { AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_SEARCH, + type: ActionTypes.RECEIVED_SEARCH, results: null }); AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POST_SELECTED, + type: ActionTypes.RECEIVED_POST_SELECTED, results: null }); diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx index 53fe7fb5d..3619a9f8f 100644 --- a/web/react/components/post.jsx +++ b/web/react/components/post.jsx @@ -31,12 +31,12 @@ export default class Post extends React.Component { data.posts = this.props.posts; AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POST_SELECTED, + type: ActionTypes.RECEIVED_POST_SELECTED, post_list: data }); AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_SEARCH, + type: ActionTypes.RECEIVED_SEARCH, results: null }); } @@ -59,7 +59,7 @@ export default class Post extends React.Component { ChannelStore.setChannelMember(member); AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POST, + type: ActionTypes.RECEIVED_POST, post: data }); }, diff --git a/web/react/components/post_deleted_modal.jsx b/web/react/components/post_deleted_modal.jsx index 218f57eb5..642befeab 100644 --- a/web/react/components/post_deleted_modal.jsx +++ b/web/react/components/post_deleted_modal.jsx @@ -24,19 +24,19 @@ export default class PostDeletedModal extends React.Component { } handleClose() { AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_SEARCH, + type: ActionTypes.RECEIVED_SEARCH, results: null }); AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_SEARCH_TERM, + type: ActionTypes.RECEIVED_SEARCH_TERM, term: null, do_search: false, is_mention_search: false }); AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POST_SELECTED, + type: ActionTypes.RECEIVED_POST_SELECTED, results: null }); } diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx index 1addebbe4..9c85e9940 100644 --- a/web/react/components/rhs_comment.jsx +++ b/web/react/components/rhs_comment.jsx @@ -49,7 +49,7 @@ class RhsComment extends React.Component { ChannelStore.setChannelMember(member); AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POST, + type: ActionTypes.RECEIVED_POST, post: data }); }, @@ -267,4 +267,4 @@ RhsComment.propTypes = { post: React.PropTypes.object }; -export default injectIntl(RhsComment);
\ No newline at end of file +export default injectIntl(RhsComment); diff --git a/web/react/components/rhs_header_post.jsx b/web/react/components/rhs_header_post.jsx index d56ba76f8..cd310df56 100644 --- a/web/react/components/rhs_header_post.jsx +++ b/web/react/components/rhs_header_post.jsx @@ -21,12 +21,12 @@ export default class RhsHeaderPost extends React.Component { e.preventDefault(); AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_SEARCH, + type: ActionTypes.RECEIVED_SEARCH, results: null }); AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POST_SELECTED, + type: ActionTypes.RECEIVED_POST_SELECTED, results: null }); } @@ -34,14 +34,14 @@ export default class RhsHeaderPost extends React.Component { e.preventDefault(); AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_SEARCH_TERM, + type: ActionTypes.RECEIVED_SEARCH_TERM, term: this.props.fromSearch, do_search: true, is_mention_search: this.props.isMentionSearch }); AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POST_SELECTED, + type: ActionTypes.RECEIVED_POST_SELECTED, results: null }); } diff --git a/web/react/components/search_bar.jsx b/web/react/components/search_bar.jsx index 35d7e9514..f7cb1b8f2 100644 --- a/web/react/components/search_bar.jsx +++ b/web/react/components/search_bar.jsx @@ -74,19 +74,19 @@ class SearchBar extends React.Component { e.preventDefault(); AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_SEARCH, + type: ActionTypes.RECEIVED_SEARCH, results: null }); AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_SEARCH_TERM, + type: ActionTypes.RECEIVED_SEARCH_TERM, term: null, do_search: false, is_mention_search: false }); AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POST_SELECTED, + type: ActionTypes.RECEIVED_POST_SELECTED, results: null }); } @@ -117,7 +117,7 @@ class SearchBar extends React.Component { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_SEARCH, + type: ActionTypes.RECEIVED_SEARCH, results: data, is_mention_search: isMentionSearch }); diff --git a/web/react/components/search_results_header.jsx b/web/react/components/search_results_header.jsx index 45f56f65a..7f88eb2c7 100644 --- a/web/react/components/search_results_header.jsx +++ b/web/react/components/search_results_header.jsx @@ -19,19 +19,19 @@ export default class SearchResultsHeader extends React.Component { e.preventDefault(); AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_SEARCH, + type: ActionTypes.RECEIVED_SEARCH, results: null }); AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_SEARCH_TERM, + type: ActionTypes.RECEIVED_SEARCH_TERM, term: null, do_search: false, is_mention_search: false }); AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POST_SELECTED, + type: ActionTypes.RECEIVED_POST_SELECTED, results: null }); } @@ -72,4 +72,4 @@ export default class SearchResultsHeader extends React.Component { SearchResultsHeader.propTypes = { isMentionSearch: React.PropTypes.bool -};
\ No newline at end of file +}; diff --git a/web/react/components/setting_item_max.jsx b/web/react/components/setting_item_max.jsx index 537055641..ac750614b 100644 --- a/web/react/components/setting_item_max.jsx +++ b/web/react/components/setting_item_max.jsx @@ -46,9 +46,14 @@ export default class SettingItemMax extends React.Component { widthClass = 'col-sm-9 col-sm-offset-3'; } + let title; + if (this.props.title) { + title = <li className='col-sm-12 section-title'>{this.props.title}</li>; + } + return ( <ul className='section-max form-horizontal'> - <li className='col-sm-12 section-title'>{this.props.title}</li> + {title} <li className={widthClass}> <ul className='setting-list'> <li className='setting-list-item'> diff --git a/web/react/components/user_settings/custom_theme_chooser.jsx b/web/react/components/user_settings/custom_theme_chooser.jsx index 9116dd938..2d88a3650 100644 --- a/web/react/components/user_settings/custom_theme_chooser.jsx +++ b/web/react/components/user_settings/custom_theme_chooser.jsx @@ -222,7 +222,7 @@ class CustomThemeChooser extends React.Component { } else { elements.push( <div - className='col-sm-4 form-group' + className='col-sm-4 form-group element' key={'custom-theme-key' + index} > <label className='custom-label'>{formatMessage(messages[element.id])}</label> @@ -265,8 +265,8 @@ class CustomThemeChooser extends React.Component { ); return ( - <div> - <div className='row form-group'> + <div className='appearance-section'> + <div className='theme-elements row form-group'> {elements} </div> <div className='row'> @@ -283,4 +283,4 @@ CustomThemeChooser.propTypes = { updateTheme: React.PropTypes.func.isRequired }; -export default injectIntl(CustomThemeChooser);
\ No newline at end of file +export default injectIntl(CustomThemeChooser); diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx index 66bed0b0b..e9e90a936 100644 --- a/web/react/components/user_settings/import_theme_modal.jsx +++ b/web/react/components/user_settings/import_theme_modal.jsx @@ -84,7 +84,7 @@ class ImportThemeModal extends React.Component { Client.updateUser(user, (data) => { AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_ME, + type: ActionTypes.RECEIVED_ME, me: data }); @@ -212,4 +212,4 @@ ImportThemeModal.propTypes = { intl: intlShape.isRequired }; -export default injectIntl(ImportThemeModal);
\ No newline at end of file +export default injectIntl(ImportThemeModal); diff --git a/web/react/components/user_settings/manage_command_hooks.jsx b/web/react/components/user_settings/manage_command_hooks.jsx index b2fc0a4e1..d23d2957e 100644 --- a/web/react/components/user_settings/manage_command_hooks.jsx +++ b/web/react/components/user_settings/manage_command_hooks.jsx @@ -530,23 +530,19 @@ export default class ManageCommandCmds extends React.Component { /> </label> <div className='padding-top'> - <label> - <input - type='checkbox' - checked={this.state.cmd.auto_complete} - onChange={this.updateAutoComplete} - /> - <FormattedMessage - id='user.settings.cmds.auto_complete_desc_desc' - defaultMessage='A short description of what this commands does' - /> - </label> - </div> - <div className='padding-top'> - <FormattedMessage - id='user.settings.cmds.auto_complete_help' - defaultMessage='Show this command in autocomplete list.' - /> + <div className='checkbox'> + <label> + <input + type='checkbox' + checked={this.state.cmd.auto_complete} + onChange={this.updateAutoComplete} + /> + <FormattedMessage + id='user.settings.cmds.auto_complete_help' + defaultMessage=' Show this command in autocomplete list' + /> + </label> + </div> </div> </div> <div className='padding-top x2'> @@ -565,12 +561,6 @@ export default class ManageCommandCmds extends React.Component { placeholder={this.props.intl.formatMessage(holders.addAutoCompleteDescPlaceholder)} /> </div> - <div className='padding-top'> - <FormattedMessage - id='user.settings.cmds.auto_complete_desc_desc' - defaultMessage='A short description of what this commands does' - /> - </div> </div> <div className='padding-top x2'> <label className='control-label'> @@ -649,7 +639,7 @@ export default class ManageCommandCmds extends React.Component { </div> {addError} </div> - <div className='padding-top padding-bottom'> + <div className='padding-top x2 padding-bottom'> <a className={'btn btn-sm btn-primary'} href='#' diff --git a/web/react/components/user_settings/premade_theme_chooser.jsx b/web/react/components/user_settings/premade_theme_chooser.jsx index 9889bff5c..80ff8c4de 100644 --- a/web/react/components/user_settings/premade_theme_chooser.jsx +++ b/web/react/components/user_settings/premade_theme_chooser.jsx @@ -45,7 +45,7 @@ export default class PremadeThemeChooser extends React.Component { } return ( - <div className='row'> + <div className='row appearance-section'> {premadeThemes} </div> ); diff --git a/web/react/components/user_settings/user_settings.jsx b/web/react/components/user_settings/user_settings.jsx index 54d98bbde..4da51fa5f 100644 --- a/web/react/components/user_settings/user_settings.jsx +++ b/web/react/components/user_settings/user_settings.jsx @@ -6,7 +6,6 @@ import * as utils from '../../utils/utils.jsx'; import NotificationsTab from './user_settings_notifications.jsx'; import SecurityTab from './user_settings_security.jsx'; import GeneralTab from './user_settings_general.jsx'; -import AppearanceTab from './user_settings_appearance.jsx'; import DeveloperTab from './user_settings_developer.jsx'; import IntegrationsTab from './user_settings_integrations.jsx'; import DisplayTab from './user_settings_display.jsx'; @@ -85,21 +84,6 @@ export default class UserSettings extends React.Component { /> </div> ); - } else if (this.props.activeTab === 'appearance') { - return ( - <div> - <AppearanceTab - ref='activeTab' - activeSection={this.props.activeSection} - updateSection={this.props.updateSection} - updateTab={this.props.updateTab} - closeModal={this.props.closeModal} - collapseModal={this.props.collapseModal} - setEnforceFocus={this.props.setEnforceFocus} - setRequireConfirm={this.props.setRequireConfirm} - /> - </div> - ); } else if (this.props.activeTab === 'developer') { return ( <div> @@ -137,6 +121,8 @@ export default class UserSettings extends React.Component { updateTab={this.props.updateTab} closeModal={this.props.closeModal} collapseModal={this.props.collapseModal} + setEnforceFocus={this.props.setEnforceFocus} + setRequireConfirm={this.props.setRequireConfirm} /> </div> ); diff --git a/web/react/components/user_settings/user_settings_display.jsx b/web/react/components/user_settings/user_settings_display.jsx index 776bde442..4b11c06fb 100644 --- a/web/react/components/user_settings/user_settings_display.jsx +++ b/web/react/components/user_settings/user_settings_display.jsx @@ -1,15 +1,18 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import {savePreferences} from '../../utils/client.jsx'; import SettingItemMin from '../setting_item_min.jsx'; import SettingItemMax from '../setting_item_max.jsx'; -import Constants from '../../utils/constants.jsx'; -const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES; -import PreferenceStore from '../../stores/preference_store.jsx'; import ManageLanguages from './manage_languages.jsx'; +import ThemeSetting from './user_settings_theme.jsx'; + +import PreferenceStore from '../../stores/preference_store.jsx'; import * as Utils from '../../utils/utils.jsx'; +import Constants from '../../utils/constants.jsx'; +const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES; + +import {savePreferences} from '../../utils/client.jsx'; import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl'; const holders = defineMessages({ @@ -452,6 +455,13 @@ class UserSettingsDisplay extends React.Component { /> </h3> <div className='divider-dark first'/> + <ThemeSetting + selected={this.props.activeSection === 'theme'} + updateSection={this.updateSection} + setRequireConfirm={this.props.setRequireConfirm} + setEnforceFocus={this.props.setEnforceFocus} + /> + <div className='divider-dark'/> {fontSection} <div className='divider-dark'/> {clockSection} @@ -472,7 +482,9 @@ UserSettingsDisplay.propTypes = { updateTab: React.PropTypes.func, activeSection: React.PropTypes.string, closeModal: React.PropTypes.func.isRequired, - collapseModal: React.PropTypes.func.isRequired + collapseModal: React.PropTypes.func.isRequired, + setRequireConfirm: React.PropTypes.func.isRequired, + setEnforceFocus: React.PropTypes.func.isRequired }; -export default injectIntl(UserSettingsDisplay);
\ No newline at end of file +export default injectIntl(UserSettingsDisplay); diff --git a/web/react/components/user_settings/user_settings_modal.jsx b/web/react/components/user_settings/user_settings_modal.jsx index 2a0a90cf5..e0b72157b 100644 --- a/web/react/components/user_settings/user_settings_modal.jsx +++ b/web/react/components/user_settings/user_settings_modal.jsx @@ -2,9 +2,13 @@ // See License.txt for license information. import ConfirmModal from '../confirm_modal.jsx'; -const Modal = ReactBootstrap.Modal; -import SettingsSidebar from '../settings_sidebar.jsx'; import UserSettings from './user_settings.jsx'; +import SettingsSidebar from '../settings_sidebar.jsx'; + +import UserStore from '../../stores/user_store.jsx'; +import * as Utils from '../../utils/utils.jsx'; + +const Modal = ReactBootstrap.Modal; import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl'; @@ -21,10 +25,6 @@ const holders = defineMessages({ id: 'user.settings.modal.notifications', defaultMessage: 'Notifications' }, - appearance: { - id: 'user.settings.modal.appearance', - defaultMessage: 'Appearance' - }, developer: { id: 'user.settings.modal.developer', defaultMessage: 'Developer' @@ -214,6 +214,12 @@ class UserSettingsModal extends React.Component { if (!skipConfirm && this.requireConfirm) { this.showConfirmModal(() => this.updateSection(section, true)); } else { + if (this.state.active_section === 'theme' && section !== 'theme') { + const user = UserStore.getCurrentUser(); + if (user.theme_props != null) { + Utils.applyTheme(user.theme_props); + } + } this.setState({active_section: section}); } } @@ -224,7 +230,6 @@ class UserSettingsModal extends React.Component { tabs.push({name: 'general', uiName: formatMessage(holders.general), icon: 'glyphicon glyphicon-cog'}); tabs.push({name: 'security', uiName: formatMessage(holders.security), icon: 'glyphicon glyphicon-lock'}); tabs.push({name: 'notifications', uiName: formatMessage(holders.notifications), icon: 'glyphicon glyphicon-exclamation-sign'}); - tabs.push({name: 'appearance', uiName: formatMessage(holders.appearance), icon: 'glyphicon glyphicon-wrench'}); if (global.window.mm_config.EnableOAuthServiceProvider === 'true') { tabs.push({name: 'developer', uiName: formatMessage(holders.developer), icon: 'glyphicon glyphicon-th'}); } @@ -294,4 +299,4 @@ UserSettingsModal.propTypes = { onModalDismissed: React.PropTypes.func.isRequired }; -export default injectIntl(UserSettingsModal);
\ No newline at end of file +export default injectIntl(UserSettingsModal); diff --git a/web/react/components/user_settings/user_settings_appearance.jsx b/web/react/components/user_settings/user_settings_theme.jsx index fb11dc81b..34c688db1 100644 --- a/web/react/components/user_settings/user_settings_appearance.jsx +++ b/web/react/components/user_settings/user_settings_theme.jsx @@ -1,8 +1,10 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. import CustomThemeChooser from './custom_theme_chooser.jsx'; import PremadeThemeChooser from './premade_theme_chooser.jsx'; +import SettingItemMin from '../setting_item_min.jsx'; +import SettingItemMax from '../setting_item_max.jsx'; import UserStore from '../../stores/user_store.jsx'; @@ -12,11 +14,22 @@ import * as Utils from '../../utils/utils.jsx'; import Constants from '../../utils/constants.jsx'; -import {FormattedMessage} from 'mm-intl'; +import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl'; const ActionTypes = Constants.ActionTypes; -export default class UserSettingsAppearance extends React.Component { +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' + } +}); + +export default class ThemeSetting extends React.Component { constructor(props) { super(props); @@ -34,16 +47,21 @@ export default class UserSettingsAppearance extends React.Component { componentDidMount() { UserStore.addChangeListener(this.onChange); - if (this.props.activeSection === 'theme') { + if (this.props.selected) { $(ReactDOM.findDOMNode(this.refs[this.state.theme])).addClass('active-border'); } } componentDidUpdate() { - if (this.props.activeSection === 'theme') { + if (this.props.selected) { $('.color-btn').removeClass('active-border'); $(ReactDOM.findDOMNode(this.refs[this.state.theme])).addClass('active-border'); } } + componentWillReceiveProps(nextProps) { + if (!this.props.selected && nextProps.selected) { + this.resetFields(); + } + } componentWillUnmount() { UserStore.removeChangeListener(this.onChange); } @@ -89,13 +107,14 @@ export default class UserSettingsAppearance extends React.Component { Client.updateUser(user, (data) => { AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_ME, + type: ActionTypes.RECEIVED_ME, me: data }); this.props.setRequireConfirm(false); this.originalTheme = Object.assign({}, this.state.theme); this.scrollToTop(); + this.props.updateSection(''); }, (err) => { var state = this.getStateFromStores(); @@ -149,6 +168,8 @@ export default class UserSettingsAppearance extends React.Component { this.props.setEnforceFocus(false); } render() { + const {formatMessage} = this.props.intl; + var serverError; if (this.state.serverError) { serverError = this.state.serverError; @@ -160,136 +181,121 @@ export default class UserSettingsAppearance extends React.Component { let premade; if (displayCustom) { custom = ( - <CustomThemeChooser - theme={this.state.theme} - updateTheme={this.updateTheme} - /> + <div key='customThemeChooser'> + <br/> + <CustomThemeChooser + theme={this.state.theme} + updateTheme={this.updateTheme} + /> + </div> ); } else { premade = ( - <PremadeThemeChooser - theme={this.state.theme} - updateTheme={this.updateTheme} - /> + <div key='premadeThemeChooser'> + <br/> + <PremadeThemeChooser + theme={this.state.theme} + updateTheme={this.updateTheme} + /> + </div> ); } - const themeUI = ( - <div className='section-max appearance-section'> - <div className='col-sm-12'> - <div className='radio'> - <label> - <input type='radio' - checked={!displayCustom} - onChange={this.updateType.bind(this, 'premade')} - /> - <FormattedMessage - id='user.settings.appearance.themeColors' - defaultMessage='Theme Colors' - /> - </label> - <br/> - </div> - {premade} - <div className='radio'> - <label> - <input type='radio' - checked={displayCustom} - onChange={this.updateType.bind(this, 'custom')} - /> - <FormattedMessage - id='user.settings.appearance.customTheme' - defaultMessage='Custom Theme' - /> - </label> - <br/> - </div> - {custom} - <hr /> - {serverError} - <a - className='btn btn-sm btn-primary' - href='#' - onClick={this.submitTheme} - > - <FormattedMessage - id='user.settings.appearance.save' - defaultMessage='Save' + let themeUI; + if (this.props.selected) { + let inputs = []; + + inputs.push( + <div + className='radio' + key='premadeThemeColorLabel' + > + <label> + <input type='radio' + checked={!displayCustom} + onChange={this.updateType.bind(this, 'premade')} /> - </a> - <a - className='btn btn-sm theme' - href='#' - onClick={this.resetFields} - > <FormattedMessage - id='user.settings.appearance.cancel' - defaultMessage='Cancel' + id='user.settings.display.theme.themeColors' + defaultMessage='Theme Colors' /> - </a> + </label> + <br/> </div> - </div> - ); + ); - return ( - <div> - <div className='modal-header'> - <button - type='button' - className='close' - aria-label='Close' - onClick={this.props.closeModal} - > - <span aria-hidden='true'>{'×'}</span> - </button> - <h4 - className='modal-title' - ref='title' - > - <i - className='modal-back' - onClick={this.props.collapseModal} + inputs.push(premade); + + inputs.push( + <div + className='radio' + key='customThemeColorLabel' + > + <label> + <input type='radio' + checked={displayCustom} + onChange={this.updateType.bind(this, 'custom')} /> <FormattedMessage - id='user.settings.appearance.title' - defaultMessage='Appearance Settings' + id='user.settings.display.theme.customTheme' + defaultMessage='Custom Theme' /> - </h4> + </label> + <br/> </div> - <div className='user-settings'> - <h3 className='tab-header'> - <FormattedMessage - id='user.settings.appearance.title' - defaultMessage='Appearance Settings' - /> - </h3> - <div className='divider-dark first'/> - {themeUI} - <div className='divider-dark'/> + ); + + inputs.push(custom); + + inputs.push( + <div key='importSlackThemeButton'> <br/> <a className='theme' onClick={this.handleImportModal} > <FormattedMessage - id='user.settings.appearance.import' + id='user.settings.display.theme.import' defaultMessage='Import theme colors from Slack' /> </a> </div> - </div> - ); + ); + + themeUI = ( + <SettingItemMax + inputs={inputs} + submit={this.submitTheme} + server_error={serverError} + width='full' + updateSection={(e) => { + this.props.updateSection(''); + e.preventDefault(); + }} + /> + ); + } else { + themeUI = ( + <SettingItemMin + title={formatMessage(holders.themeTitle)} + describe={formatMessage(holders.themeDescribe)} + updateSection={() => { + this.props.updateSection('theme'); + }} + /> + ); + } + + return themeUI; } } -UserSettingsAppearance.defaultProps = { - activeSection: '' -}; -UserSettingsAppearance.propTypes = { - activeSection: React.PropTypes.string, - updateTab: React.PropTypes.func, - closeModal: React.PropTypes.func.isRequired, - collapseModal: React.PropTypes.func.isRequired, +ThemeSetting.propTypes = { + intl: intlShape.isRequired, + selected: React.PropTypes.bool.isRequired, + updateSection: React.PropTypes.func.isRequired, setRequireConfirm: React.PropTypes.func.isRequired, setEnforceFocus: React.PropTypes.func.isRequired }; + +export default injectIntl(ThemeSetting); diff --git a/web/react/dispatcher/event_helpers.jsx b/web/react/dispatcher/event_helpers.jsx index c1041e438..5476d707f 100644 --- a/web/react/dispatcher/event_helpers.jsx +++ b/web/react/dispatcher/event_helpers.jsx @@ -30,7 +30,7 @@ export function emitPostFocusEvent(postId) { postId, (data) => { AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_FOCUSED_POST, + type: ActionTypes.RECEIVED_FOCUSED_POST, postId, post_list: data }); @@ -47,13 +47,13 @@ export function emitPostFocusRightHandSideFromSearch(post, isMentionSearch) { post.id, (data) => { AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POST_SELECTED, + type: ActionTypes.RECEIVED_POST_SELECTED, post_list: data, from_search: SearchStore.getSearchTerm() }); AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_SEARCH, + type: ActionTypes.RECEIVED_SEARCH, results: null, is_mention_search: isMentionSearch }); @@ -89,7 +89,7 @@ export function emitLoadMorePostsFocusedBottomEvent() { export function emitPostRecievedEvent(post) { AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POST, + type: ActionTypes.RECEIVED_POST, post }); } @@ -177,7 +177,7 @@ export function emitClearSuggestions(suggestionId) { export function emitPreferenceChangedEvent(preference) { AppDispatcher.handleServerAction({ - type: Constants.ActionTypes.RECIEVED_PREFERENCE, + type: Constants.ActionTypes.RECEIVED_PREFERENCE, preference }); } diff --git a/web/react/stores/admin_store.jsx b/web/react/stores/admin_store.jsx index 8f43091a7..eb3254cfe 100644 --- a/web/react/stores/admin_store.jsx +++ b/web/react/stores/admin_store.jsx @@ -135,19 +135,19 @@ AdminStoreClass.dispatchToken = AppDispatcher.register((payload) => { var action = payload.action; switch (action.type) { - case ActionTypes.RECIEVED_LOGS: + case ActionTypes.RECEIVED_LOGS: AdminStore.saveLogs(action.logs); AdminStore.emitLogChange(); break; - case ActionTypes.RECIEVED_SERVER_AUDITS: + case ActionTypes.RECEIVED_SERVER_AUDITS: AdminStore.saveAudits(action.audits); AdminStore.emitAuditChange(); break; - case ActionTypes.RECIEVED_CONFIG: + case ActionTypes.RECEIVED_CONFIG: AdminStore.saveConfig(action.config); AdminStore.emitConfigChange(); break; - case ActionTypes.RECIEVED_ALL_TEAMS: + case ActionTypes.RECEIVED_ALL_TEAMS: AdminStore.saveAllTeams(action.teams); AdminStore.emitAllTeamsChange(); break; diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx index 2337a573d..d650b23c2 100644 --- a/web/react/stores/channel_store.jsx +++ b/web/react/stores/channel_store.jsx @@ -296,7 +296,7 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => { ChannelStore.emitChange(); break; - case ActionTypes.RECIEVED_FOCUSED_POST: { + case ActionTypes.RECEIVED_FOCUSED_POST: { const post = action.post_list.posts[action.postId]; ChannelStore.setCurrentId(post.channel_id); ChannelStore.setPostMode(ChannelStore.POST_MODE_FOCUS); @@ -304,7 +304,7 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => { break; } - case ActionTypes.RECIEVED_CHANNELS: + case ActionTypes.RECEIVED_CHANNELS: ChannelStore.storeChannels(action.channels); ChannelStore.storeChannelMembers(action.members); currentId = ChannelStore.getCurrentId(); @@ -315,7 +315,7 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => { ChannelStore.emitChange(); break; - case ActionTypes.RECIEVED_CHANNEL: + case ActionTypes.RECEIVED_CHANNEL: ChannelStore.pStoreChannel(action.channel); if (action.member) { ChannelStore.pStoreChannelMember(action.member); @@ -328,12 +328,12 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => { ChannelStore.emitChange(); break; - case ActionTypes.RECIEVED_MORE_CHANNELS: + case ActionTypes.RECEIVED_MORE_CHANNELS: ChannelStore.storeMoreChannels(action.channels); ChannelStore.emitMoreChange(); break; - case ActionTypes.RECIEVED_CHANNEL_EXTRA_INFO: + case ActionTypes.RECEIVED_CHANNEL_EXTRA_INFO: var extraInfos = ChannelStore.getExtraInfos(); extraInfos[action.extra_info.id] = action.extra_info; ChannelStore.storeExtraInfos(extraInfos); diff --git a/web/react/stores/error_store.jsx b/web/react/stores/error_store.jsx index ed46d6b68..5afcefd12 100644 --- a/web/react/stores/error_store.jsx +++ b/web/react/stores/error_store.jsx @@ -57,7 +57,7 @@ var ErrorStore = new ErrorStoreClass(); ErrorStore.dispatchToken = AppDispatcher.register((payload) => { var action = payload.action; switch (action.type) { - case ActionTypes.RECIEVED_ERROR: + case ActionTypes.RECEIVED_ERROR: ErrorStore.storeLastError(action.err); ErrorStore.emitChange(); break; diff --git a/web/react/stores/file_store.jsx b/web/react/stores/file_store.jsx index ca8c6a96b..6d7e0f354 100644 --- a/web/react/stores/file_store.jsx +++ b/web/react/stores/file_store.jsx @@ -49,7 +49,7 @@ class FileStore extends EventEmitter { const action = payload.action; switch (action.type) { - case ActionTypes.RECIEVED_FILE_INFO: + case ActionTypes.RECEIVED_FILE_INFO: this.setInfo(action.filename, action.info); this.emitChange(action.filename); break; diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx index 8ff58f685..b0f421b60 100644 --- a/web/react/stores/post_store.jsx +++ b/web/react/stores/post_store.jsx @@ -540,23 +540,23 @@ PostStore.dispatchToken = AppDispatcher.register((payload) => { var action = payload.action; switch (action.type) { - case ActionTypes.RECIEVED_POSTS: { + case ActionTypes.RECEIVED_POSTS: { const id = PostStore.currentFocusedPostId == null ? action.id : PostStore.currentFocusedPostId; PostStore.checkBounds(id, action.numRequested, makePostListNonNull(action.post_list), action.before); PostStore.storePosts(id, makePostListNonNull(action.post_list)); PostStore.emitChange(); break; } - case ActionTypes.RECIEVED_FOCUSED_POST: + case ActionTypes.RECEIVED_FOCUSED_POST: PostStore.clearChannelVisibility(action.postId, false); PostStore.storeFocusedPost(action.postId, makePostListNonNull(action.post_list)); PostStore.emitChange(); break; - case ActionTypes.RECIEVED_POST: + case ActionTypes.RECEIVED_POST: PostStore.storePost(action.post); PostStore.emitChange(); break; - case ActionTypes.RECIEVED_EDIT_POST: + case ActionTypes.RECEIVED_EDIT_POST: PostStore.emitEditPost(action); PostStore.emitChange(); break; @@ -577,7 +577,7 @@ PostStore.dispatchToken = AppDispatcher.register((payload) => { PostStore.removePost(action.post); PostStore.emitChange(); break; - case ActionTypes.RECIEVED_POST_SELECTED: + case ActionTypes.RECEIVED_POST_SELECTED: PostStore.storeSelectedPost(action.post_list); PostStore.emitSelectedPostChange(action.from_search); break; diff --git a/web/react/stores/preference_store.jsx b/web/react/stores/preference_store.jsx index 7ecaf0a95..30f5468c2 100644 --- a/web/react/stores/preference_store.jsx +++ b/web/react/stores/preference_store.jsx @@ -159,13 +159,13 @@ class PreferenceStoreClass extends EventEmitter { const action = payload.action; switch (action.type) { - case ActionTypes.RECIEVED_PREFERENCE: { + case ActionTypes.RECEIVED_PREFERENCE: { const preference = action.preference; this.setPreference(preference.category, preference.name, preference.value); this.emitChange(); break; } - case ActionTypes.RECIEVED_PREFERENCES: + case ActionTypes.RECEIVED_PREFERENCES: this.setPreferences(action.preferences); this.emitChange(); break; diff --git a/web/react/stores/search_store.jsx b/web/react/stores/search_store.jsx index f932c379a..549f355ef 100644 --- a/web/react/stores/search_store.jsx +++ b/web/react/stores/search_store.jsx @@ -119,11 +119,11 @@ SearchStore.dispatchToken = AppDispatcher.register((payload) => { var action = payload.action; switch (action.type) { - case ActionTypes.RECIEVED_SEARCH: + case ActionTypes.RECEIVED_SEARCH: SearchStore.storeSearchResults(action.results, action.is_mention_search); SearchStore.emitSearchChange(); break; - case ActionTypes.RECIEVED_SEARCH_TERM: + case ActionTypes.RECEIVED_SEARCH_TERM: SearchStore.storeSearchTerm(action.term); SearchStore.emitSearchTermChange(action.do_search, action.is_mention_search); break; diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx index 9c3270f68..bc2bdbe64 100644 --- a/web/react/stores/socket_store.jsx +++ b/web/react/stores/socket_store.jsx @@ -28,10 +28,13 @@ class SocketStoreClass extends EventEmitter { this.addChangeListener = this.addChangeListener.bind(this); this.removeChangeListener = this.removeChangeListener.bind(this); this.sendMessage = this.sendMessage.bind(this); + this.close = this.close.bind(this); + this.failCount = 0; this.initialize(); } + initialize() { if (!UserStore.getCurrentId()) { return; @@ -106,15 +109,19 @@ class SocketStoreClass extends EventEmitter { }; } } + emitChange(msg) { this.emit(CHANGE_EVENT, msg); } + addChangeListener(callback) { this.on(CHANGE_EVENT, callback); } + removeChangeListener(callback) { this.removeListener(CHANGE_EVENT, callback); } + handleMessage(msg) { switch (msg.action) { case SocketEvents.POSTED: @@ -153,6 +160,7 @@ class SocketStoreClass extends EventEmitter { default: } } + sendMessage(msg) { if (conn && conn.readyState === WebSocket.OPEN) { conn.send(JSON.stringify(msg)); @@ -161,9 +169,16 @@ class SocketStoreClass extends EventEmitter { this.initialize(); } } + setTranslations(messages) { this.translations = messages; } + + close() { + if (conn && conn.readyState === WebSocket.OPEN) { + conn.close(); + } + } } function handleNewPostEvent(msg, translations) { @@ -305,12 +320,5 @@ function handlePreferenceChangedEvent(msg) { var SocketStore = new SocketStoreClass(); -/*SocketStore.dispatchToken = AppDispatcher.register((payload) => { - var action = payload.action; - - switch (action.type) { - default: - } - });*/ - export default SocketStore; +window.SocketStore = SocketStore; diff --git a/web/react/stores/team_store.jsx b/web/react/stores/team_store.jsx index 2d518d9e7..7a1a2ef42 100644 --- a/web/react/stores/team_store.jsx +++ b/web/react/stores/team_store.jsx @@ -116,7 +116,7 @@ TeamStore.dispatchToken = AppDispatcher.register((payload) => { var action = payload.action; switch (action.type) { - case ActionTypes.RECIEVED_TEAM: + case ActionTypes.RECEIVED_TEAM: TeamStore.saveTeam(action.team); TeamStore.emitChange(); break; diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx index b97a0d87b..dd60e166f 100644 --- a/web/react/stores/user_store.jsx +++ b/web/react/stores/user_store.jsx @@ -336,27 +336,27 @@ UserStore.dispatchToken = AppDispatcher.register((payload) => { var action = payload.action; switch (action.type) { - case ActionTypes.RECIEVED_PROFILES: + case ActionTypes.RECEIVED_PROFILES: UserStore.saveProfiles(action.profiles); UserStore.emitChange(); break; - case ActionTypes.RECIEVED_ME: + case ActionTypes.RECEIVED_ME: UserStore.setCurrentUser(action.me); UserStore.emitChange(action.me.id); break; - case ActionTypes.RECIEVED_SESSIONS: + case ActionTypes.RECEIVED_SESSIONS: UserStore.setSessions(action.sessions); UserStore.emitSessionsChange(); break; - case ActionTypes.RECIEVED_AUDITS: + case ActionTypes.RECEIVED_AUDITS: UserStore.setAudits(action.audits); UserStore.emitAuditsChange(); break; - case ActionTypes.RECIEVED_TEAMS: + case ActionTypes.RECEIVED_TEAMS: UserStore.setTeams(action.teams); UserStore.emitTeamsChange(); break; - case ActionTypes.RECIEVED_STATUSES: + case ActionTypes.RECEIVED_STATUSES: UserStore.pSetStatuses(action.statuses); UserStore.emitStatusesChange(); break; diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx index 7cada98b7..d5fc10b8f 100644 --- a/web/react/utils/async_client.jsx +++ b/web/react/utils/async_client.jsx @@ -18,7 +18,7 @@ var callTracker = {}; export function dispatchError(err, method) { AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_ERROR, + type: ActionTypes.RECEIVED_ERROR, err: err, method: method }); @@ -71,7 +71,7 @@ export function getChannels(checkVersion) { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_CHANNELS, + type: ActionTypes.RECEIVED_CHANNELS, channels: data.channels, members: data.members }); @@ -99,7 +99,7 @@ export function getChannel(id) { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_CHANNEL, + type: ActionTypes.RECEIVED_CHANNEL, channel: data.channel, member: data.member }); @@ -156,7 +156,7 @@ export function getMoreChannels(force) { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_MORE_CHANNELS, + type: ActionTypes.RECEIVED_MORE_CHANNELS, channels: data.channels, members: data.members }); @@ -195,7 +195,7 @@ export function getChannelExtraInfo(id, memberLimit) { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_CHANNEL_EXTRA_INFO, + type: ActionTypes.RECEIVED_CHANNEL_EXTRA_INFO, extra_info: data }); }, @@ -222,7 +222,7 @@ export function getProfiles() { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_PROFILES, + type: ActionTypes.RECEIVED_PROFILES, profiles: data }); }, @@ -249,7 +249,7 @@ export function getSessions() { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_SESSIONS, + type: ActionTypes.RECEIVED_SESSIONS, sessions: data }); }, @@ -276,7 +276,7 @@ export function getAudits() { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_AUDITS, + type: ActionTypes.RECEIVED_AUDITS, audits: data }); }, @@ -302,7 +302,7 @@ export function getLogs() { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_LOGS, + type: ActionTypes.RECEIVED_LOGS, logs: data }); }, @@ -328,7 +328,7 @@ export function getServerAudits() { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_SERVER_AUDITS, + type: ActionTypes.RECEIVED_SERVER_AUDITS, audits: data }); }, @@ -354,7 +354,7 @@ export function getConfig() { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_CONFIG, + type: ActionTypes.RECEIVED_CONFIG, config: data }); }, @@ -380,7 +380,7 @@ export function getAllTeams() { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_ALL_TEAMS, + type: ActionTypes.RECEIVED_ALL_TEAMS, teams: data }); }, @@ -409,7 +409,7 @@ export function findTeams(email) { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_TEAMS, + type: ActionTypes.RECEIVED_TEAMS, teams: data }); }, @@ -437,7 +437,7 @@ export function search(terms) { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_SEARCH, + type: ActionTypes.RECEIVED_SEARCH, results: data }); }, @@ -489,7 +489,7 @@ export function getPostsPage(id, maxPosts) { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POSTS, + type: ActionTypes.RECEIVED_POSTS, id: channelId, before: true, numRequested: numPosts, @@ -539,7 +539,7 @@ export function getPosts(id) { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POSTS, + type: ActionTypes.RECEIVED_POSTS, id: channelId, before: true, numRequested: Constants.POST_CHUNK_SIZE, @@ -578,7 +578,7 @@ export function getPostsBefore(postId, offset, numPost) { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POSTS, + type: ActionTypes.RECEIVED_POSTS, id: channelId, before: true, numRequested: numPost, @@ -617,7 +617,7 @@ export function getPostsAfter(postId, offset, numPost) { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POSTS, + type: ActionTypes.RECEIVED_POSTS, id: channelId, before: false, numRequested: numPost, @@ -650,7 +650,7 @@ export function getMe() { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_ME, + type: ActionTypes.RECEIVED_ME, me: data }); }, @@ -685,7 +685,7 @@ export function getStatuses() { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_STATUSES, + type: ActionTypes.RECEIVED_STATUSES, statuses: data }); }, @@ -711,7 +711,7 @@ export function getMyTeam() { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_TEAM, + type: ActionTypes.RECEIVED_TEAM, team: data }); }, @@ -737,7 +737,7 @@ export function getAllPreferences() { } AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_PREFERENCES, + type: ActionTypes.RECEIVED_PREFERENCES, preferences: data }); }, @@ -754,7 +754,7 @@ export function savePreferences(preferences, success, error) { (data, textStatus, xhr) => { if (xhr.status !== 304) { AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_PREFERENCES, + type: ActionTypes.RECEIVED_PREFERENCES, preferences }); } @@ -821,7 +821,7 @@ export function getFileInfo(filename) { callTracker[callName] = 0; AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_FILE_INFO, + type: ActionTypes.RECEIVED_FILE_INFO, filename, info: data }); diff --git a/web/react/utils/channel_intro_messages.jsx b/web/react/utils/channel_intro_messages.jsx index 69e08f143..1aca0467e 100644 --- a/web/react/utils/channel_intro_messages.jsx +++ b/web/react/utils/channel_intro_messages.jsx @@ -128,7 +128,7 @@ export function createDefaultIntroMessage(channel) { <div className='channel-intro'> <FormattedHTMLMessage id='intro_messages.default' - defaultMessage="<h4 class='channel-intro__title'>Beginning of {display_name}</h4><p class='channel-intro__content'><strong>Welcome to {display_name}!'</strong><br/><br/>This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.</p>" + defaultMessage="<h4 class='channel-intro__title'>Beginning of {display_name}</h4><p class='channel-intro__content'><strong>Welcome to {display_name}!</strong><br/><br/>This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.</p>" values={{ display_name: channel.display_name }} diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index d78776aa3..d7d8a2ced 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -5,7 +5,7 @@ import keyMirror from 'keymirror'; export default { ActionTypes: keyMirror({ - RECIEVED_ERROR: null, + RECEIVED_ERROR: null, CLICK_CHANNEL: null, CREATE_CHANNEL: null, @@ -14,40 +14,40 @@ export default { POST_DELETED: null, REMOVE_POST: null, - RECIEVED_CHANNELS: null, - RECIEVED_CHANNEL: null, - RECIEVED_MORE_CHANNELS: null, - RECIEVED_CHANNEL_EXTRA_INFO: null, + RECEIVED_CHANNELS: null, + RECEIVED_CHANNEL: null, + RECEIVED_MORE_CHANNELS: null, + RECEIVED_CHANNEL_EXTRA_INFO: null, FOCUS_POST: null, - RECIEVED_POSTS: null, - RECIEVED_FOCUSED_POST: null, - RECIEVED_POST: null, - RECIEVED_EDIT_POST: null, - RECIEVED_SEARCH: null, - RECIEVED_SEARCH_TERM: null, - RECIEVED_POST_SELECTED: null, - RECIEVED_MENTION_DATA: null, - RECIEVED_ADD_MENTION: null, + RECEIVED_POSTS: null, + RECEIVED_FOCUSED_POST: null, + RECEIVED_POST: null, + RECEIVED_EDIT_POST: null, + RECEIVED_SEARCH: null, + RECEIVED_SEARCH_TERM: null, + RECEIVED_POST_SELECTED: null, + RECEIVED_MENTION_DATA: null, + RECEIVED_ADD_MENTION: null, - RECIEVED_PROFILES: null, - RECIEVED_ME: null, - RECIEVED_SESSIONS: null, - RECIEVED_AUDITS: null, - RECIEVED_TEAMS: null, - RECIEVED_STATUSES: null, - RECIEVED_PREFERENCE: null, - RECIEVED_PREFERENCES: null, - RECIEVED_FILE_INFO: null, + RECEIVED_PROFILES: null, + RECEIVED_ME: null, + RECEIVED_SESSIONS: null, + RECEIVED_AUDITS: null, + RECEIVED_TEAMS: null, + RECEIVED_STATUSES: null, + RECEIVED_PREFERENCE: null, + RECEIVED_PREFERENCES: null, + RECEIVED_FILE_INFO: null, - RECIEVED_MSG: null, + RECEIVED_MSG: null, - RECIEVED_TEAM: null, + RECEIVED_TEAM: null, - RECIEVED_CONFIG: null, - RECIEVED_LOGS: null, - RECIEVED_SERVER_AUDITS: null, - RECIEVED_ALL_TEAMS: null, + RECEIVED_CONFIG: null, + RECEIVED_LOGS: null, + RECEIVED_SERVER_AUDITS: null, + RECEIVED_ALL_TEAMS: null, SHOW_SEARCH: null, diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 4beec8d64..238a209a4 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -490,7 +490,7 @@ export function insertHtmlEntities(text) { export function searchForTerm(term) { AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_SEARCH_TERM, + type: ActionTypes.RECEIVED_SEARCH_TERM, term: term, do_search: true }); diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss index a13689382..ee6a6b955 100644 --- a/web/sass-files/sass/partials/_base.scss +++ b/web/sass-files/sass/partials/_base.scss @@ -25,6 +25,10 @@ body { } } +b, strong { + font-weight: 600; +} + .inner__wrap { height: 100%; > .row.main { diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss index a018315e3..cc22cc913 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -485,8 +485,9 @@ body.ios { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; position: absolute; - top: -3px; - left: -1.0em; + top: -2px; + left: -7px; + font-size: 11px; line-height: 37px; @include opacity(0); } @@ -570,11 +571,20 @@ body.ios { li { display: inline-block; + vertical-align: top; } .col__name { margin-right: 7px; font-weight: 600; + + .user-popover { + max-width: 200px; + @include clearfix; + text-overflow: ellipsis; + white-space: nowrap; + } + } .col__reply { diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss index 09d498a69..5d6cbee60 100644 --- a/web/sass-files/sass/partials/_responsive.scss +++ b/web/sass-files/sass/partials/_responsive.scss @@ -160,6 +160,11 @@ .col__name { pointer-events: none; + + .user-popover { + max-width: 130px; + } + } } diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss index bd47ca960..bf296e913 100644 --- a/web/sass-files/sass/partials/_settings.scss +++ b/web/sass-files/sass/partials/_settings.scss @@ -178,9 +178,17 @@ } } } + + .theme-elements { + padding-left:15px; + .element { + margin-right:10px; + } + } + .custom-label { font-weight: normal; - font-size: 13px; + font-size: 12px; width: 100%; overflow: hidden; text-overflow: ellipsis; diff --git a/web/sass-files/sass/partials/_sidebar--right.scss b/web/sass-files/sass/partials/_sidebar--right.scss index f40a50b03..aaa6f4c92 100644 --- a/web/sass-files/sass/partials/_sidebar--right.scss +++ b/web/sass-files/sass/partials/_sidebar--right.scss @@ -16,6 +16,22 @@ } + .post { + + .post__header { + + .col__name { + + .user-popover { + max-width: 130px; + } + + } + + } + + } + .sidebar--right__content { height: 100%; @include display-flex; diff --git a/web/static/i18n/en.json b/web/static/i18n/en.json index caa4afae3..64d06f46d 100644 --- a/web/static/i18n/en.json +++ b/web/static/i18n/en.json @@ -365,6 +365,8 @@ "admin.service.developerDesc": "(Developer Option) When true, extra information around errors will be displayed in the UI.", "admin.service.securityTitle": "Enable Security Alerts: ", "admin.service.securityDesc": "When true, System Administrators are notified by email if a relevant security fix alert has been announced in the last 12 hours. Requires email to be enabled.", + "admin.service.insecureTlsTitle": "Enable Insecure Outgoing Connections: ", + "admin.service.insecureTlsDesc": "When true, any outgoing HTTPS requests will accept unverified, self-signed certificates. For example, outgoing webhooks to a server with a self-signed TLS certificate, using any domain, will be allowed. Note that this makes these connections susceptible to man-in-the-middle attacks.", "admin.service.webSessionDays": "Session Length for Web in Days:", "admin.service.webSessionDaysDesc": "The web session will expire after the number of days specified and will require a user to login again.", "admin.service.mobileSessionDays": "Session Length for Mobile Device in Days:", @@ -1087,8 +1089,7 @@ "user.settings.cmds.username_desc": "The username to use when overriding the post.", "user.settings.cmds.icon_url_desc": "URL to an icon", "user.settings.cmds.trigger_desc": "Word to trigger on", - "user.settings.cmds.auto_complete_desc_desc": "A short description of what this commands does", - "user.settings.cmds.auto_complete_help": "Show this command in autocomplete list.", + "user.settings.cmds.auto_complete_help": "Show this command in autocomplete list", "user.settings.cmds.auto_complete_hint_desc": "List parameters to be passed to the command.", "user.settings.cmds.request_type_desc": "Command request type issued to the callback URL.", "user.settings.cmds.url_desc": "URL that will receive the HTTP POST or GET event", @@ -1129,12 +1130,6 @@ "user.settings.advance.sendDesc": "If enabled 'Enter' inserts a new line and 'Ctrl + Enter' submits the message.", "user.settings.advance.preReleaseDesc": "Check any pre-released features you'd like to preview. You may also need to refresh the page before the setting will take effect.", "user.settings.advance.title": "Advanced Settings", - "user.settings.appearance.themeColors": "Theme Colors", - "user.settings.appearance.customTheme": "Custom Theme", - "user.settings.appearance.save": "Save", - "user.settings.appearance.cancel": "Cancel", - "user.settings.appearance.title": "Appearance Settings", - "user.settings.appearance.import": "Import theme colors from Slack", "user.settings.developer.applicationsPreview": "Applications (Preview)", "user.settings.developer.thirdParty": "Open to register a new third-party application", "user.settings.developer.register": "Register New Application", @@ -1194,7 +1189,6 @@ "user.settings.modal.general": "General", "user.settings.modal.security": "Security", "user.settings.modal.notifications": "Notifications", - "user.settings.modal.appearance": "Appearance", "user.settings.modal.developer": "Developer", "user.settings.modal.integrations": "Integrations", "user.settings.modal.display": "Display", @@ -1245,6 +1239,11 @@ "user.settings.security.title": "Security Settings", "user.settings.security.viewHistory": "View Access History", "user.settings.security.logoutActiveSessions": "View and Logout of Active Sessions", + "user.settings.display.theme.title": "Theme", + "user.settings.display.theme.describe": "Open to manage your theme", + "user.settings.display.theme.themeColors": "Theme Colors", + "user.settings.display.theme.customTheme": "Custom Theme", + "user.settings.display.theme.import": "Import theme colors from Slack", "view_image_popover.publicLink": "Get Public Link", "view_image_popover.file": "File {count} of {total}", "view_image_popover.download": "Download", @@ -1253,7 +1252,7 @@ "intro_messages.teammate": "This is the start of your direct message history with this teammate. Direct messages and files shared here are not shown to people outside this area.", "intro_messages.offTopic": "<h4 class=\"channel-intro__title\">Beginning of {display_name}</h4><p class=\"channel-intro__content\">This is the start of {display_name}, a channel for non-work-related conversations.<br/></p>", "intro_messages.inviteOthers": "Invite others to this team", - "intro_messages.default": "<h4 class='channel-intro__title'>Beginning of {display_name}</h4><p class='channel-intro__content'><strong>Welcome to {display_name}!'</strong><br/><br/>This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.</p>", + "intro_messages.default": "<h4 class='channel-intro__title'>Beginning of {display_name}</h4><p class='channel-intro__content'><strong>Welcome to {display_name}!</strong><br/><br/>This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.</p>", "intro_messages.group": "private group", "intro_messages.onlyInvited": " Only invited members can see this private group.", "intro_messages.channel": "channel", diff --git a/web/static/i18n/es.json b/web/static/i18n/es.json index b22a7cfd2..a65b20e4c 100644 --- a/web/static/i18n/es.json +++ b/web/static/i18n/es.json @@ -303,6 +303,8 @@ "admin.service.googleTitle": "Llave de desarrolador Google:", "admin.service.iconDescription": "Cuando es verdadero, se le permitirá cambiar el icono del mensaje desde webhooks. Nota, en combinación con permitir el cambio de nombre de usuario, podría exponer a los usuarios a sufrir ataques de phishing.", "admin.service.iconTitle": "Habilitar el cambio de icono desde los Webhooks: ", + "admin.service.insecureTlsDesc": "Cuando es verdadero, cualquier solicitud de salida por HTTPS será aceptada incluso si posee certificados no verificados o autofirmados. Por ejemplo, webhooks de salida a un servidor con un certificado TLS autofirmado, utilizando cualquier dominio, será permitido. Tenga en cuenta que esto hace que estas conexiones susceptibles a los ataques hombre-en-el-medio.", + "admin.service.insecureTlsTitle": "Habilitar Conexiones de Salida Inseguras: ", "admin.service.integrationAdmin": "Habilitar Integraciones sólo para administradores: ", "admin.service.integrationAdminDesc": "Cuando es verdadero, las integraciones creadas por usuarios solo pueden ser creadas por administradores.", "admin.service.listenAddress": "Dirección de escucha:", @@ -1057,12 +1059,6 @@ "user.settings.advance.sendDesc": "Si está habilitado 'Retorno' inserta una nueva linea y 'Ctrl + Retorno' envía el mensaje.", "user.settings.advance.sendTitle": "Enviar mensajes con Ctrl + Retorno", "user.settings.advance.title": "Configuración Avanzada", - "user.settings.appearance.cancel": "Cancelar", - "user.settings.appearance.customTheme": "Tema personalizado", - "user.settings.appearance.import": "Importar los colores de tema de Slack", - "user.settings.appearance.save": "Guardar", - "user.settings.appearance.themeColors": "Selecciona un Tema", - "user.settings.appearance.title": "Configuraciones de Apariencia", "user.settings.cmds.add": "Agregar", "user.settings.cmds.add_desc": "Crea comandos que permitan enviar eventos a integraciones externas. Por favor revisa <a href=\"http://mattermost.org/commands\">http://mattermost.org/commands</a> para aprender más.", "user.settings.cmds.add_display_name.placeholder": "Nombre a mostrar", @@ -1074,7 +1070,6 @@ "user.settings.cmds.auto_complete.yes": "sí", "user.settings.cmds.auto_complete_desc": "Descripción del Auto Completado: ", "user.settings.cmds.auto_complete_desc.placeholder": "Una pequeña descripción de que hace el comando.", - "user.settings.cmds.auto_complete_desc_desc": "Una pequeña descripción de que hace el comando", "user.settings.cmds.auto_complete_help": "Mostrar este comando en la lista de auto completado.", "user.settings.cmds.auto_complete_hint": "Pista de auto completado: ", "user.settings.cmds.auto_complete_hint.placeholder": "[código postal]", @@ -1136,6 +1131,11 @@ "user.settings.display.showNickname": "Mostrar el sobrenombre si existe, de lo contrario mostrar el nombre y apellido", "user.settings.display.showUsername": "Mostrar el nombre de usuario (predeterminado)", "user.settings.display.teammateDisplay": "Visualización del nombre de los integrantes", + "user.settings.display.theme.customTheme": "Tema Personalizado", + "user.settings.display.theme.describe": "Abrir para administrar tu tema", + "user.settings.display.theme.import": "Importar colores del tema desde Slack", + "user.settings.display.theme.themeColors": "Colores del Tema", + "user.settings.display.theme.title": "Tema", "user.settings.display.title": "Configuración de Visualización", "user.settings.general.checkEmail": "Revisa tu correo electrónico {email} para verificar la dirección.", "user.settings.general.checkEmailNoAddress": "Revisa tu correo electrónico para verificar la dirección", @@ -1205,7 +1205,6 @@ "user.settings.languages": "Cambiar Idioma", "user.settings.languages.change": "Cambia el idioma con el que se muestra la intefaz de usuario", "user.settings.modal.advanced": "Avanzada", - "user.settings.modal.appearance": "Apariencia", "user.settings.modal.confirmBtns": "Sí, Descartar", "user.settings.modal.confirmMsg": "Tienes cambios sin guardar, ¿Estás seguro que los quieres descartar?", "user.settings.modal.confirmTitle": "¿Descartar Cambios?", diff --git a/web/templates/head.html b/web/templates/head.html index b1ec905b5..da65e1779 100644 --- a/web/templates/head.html +++ b/web/templates/head.html @@ -122,6 +122,12 @@ } }); }); + + $(window).on('beforeunload', function(){ + if (window.SocketStore) { + SocketStore.close(); + } + }); </script> <script> |