summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/api.go2
-rw-r--r--api/command_msg.go95
-rw-r--r--api/command_msg_test.go51
-rw-r--r--api/context.go36
-rw-r--r--i18n/en.json170
-rw-r--r--i18n/es.json76
-rw-r--r--i18n/fr.json4
-rw-r--r--i18n/pt.json78
-rw-r--r--store/sql_channel_store.go11
-rw-r--r--templates/error.html24
-rw-r--r--webapp/components/about_build_modal.jsx133
-rw-r--r--webapp/components/admin_console/compliance_settings.jsx2
-rw-r--r--webapp/components/admin_console/service_settings.jsx2
-rw-r--r--webapp/components/admin_console/user_item.jsx2
-rw-r--r--webapp/components/analytics/team_analytics.jsx2
-rw-r--r--webapp/components/backstage/add_incoming_webhook.jsx166
-rw-r--r--webapp/components/backstage/add_integration.jsx20
-rw-r--r--webapp/components/backstage/add_integration_option.jsx8
-rw-r--r--webapp/components/backstage/add_outgoing_webhook.jsx240
-rw-r--r--webapp/components/backstage/backstage_category.jsx2
-rw-r--r--webapp/components/backstage/backstage_navbar.jsx4
-rw-r--r--webapp/components/backstage/backstage_sidebar.jsx2
-rw-r--r--webapp/components/backstage/installed_incoming_webhook.jsx16
-rw-r--r--webapp/components/backstage/installed_integrations.jsx42
-rw-r--r--webapp/components/backstage/installed_outgoing_webhook.jsx14
-rw-r--r--webapp/components/error_page.jsx58
-rw-r--r--webapp/components/post.jsx23
-rw-r--r--webapp/components/post_body_additional_content.jsx2
-rw-r--r--webapp/components/posts_view.jsx6
-rw-r--r--webapp/components/rhs_root_post.jsx13
-rw-r--r--webapp/components/search_results_item.jsx35
-rw-r--r--webapp/components/sidebar.jsx24
-rw-r--r--webapp/components/suggestion/at_mention_provider.jsx15
-rw-r--r--webapp/components/tutorial/tutorial_intro_screens.jsx16
-rw-r--r--webapp/components/tutorial/tutorial_view.jsx31
-rw-r--r--webapp/components/user_settings/user_settings_display.jsx2
-rw-r--r--webapp/components/user_settings/user_settings_security.jsx3
-rw-r--r--webapp/i18n/en.json69
-rw-r--r--webapp/i18n/es.json91
-rw-r--r--webapp/i18n/fr.json4
-rw-r--r--webapp/i18n/pt.json62
-rw-r--r--webapp/root.jsx25
-rw-r--r--webapp/sass/layout/_forms.scss2
-rw-r--r--webapp/sass/layout/_post.scss9
-rw-r--r--webapp/sass/responsive/_desktop.scss5
-rw-r--r--webapp/sass/responsive/_mobile.scss72
-rw-r--r--webapp/sass/responsive/_tablet.scss13
-rw-r--r--webapp/sass/routes/_about-modal.scss78
-rw-r--r--webapp/sass/routes/_backstage.scss245
-rw-r--r--webapp/sass/routes/_module.scss1
-rw-r--r--webapp/sass/utils/_variables.scss2
-rw-r--r--webapp/stores/post_store.jsx4
-rw-r--r--webapp/utils/constants.jsx3
-rw-r--r--webapp/utils/utils.jsx55
-rw-r--r--webapp/webpack.config.js4
55 files changed, 1544 insertions, 630 deletions
diff --git a/api/api.go b/api/api.go
index 20f77e558..476047877 100644
--- a/api/api.go
+++ b/api/api.go
@@ -27,6 +27,8 @@ func InitApi() {
InitWebhook(r)
InitPreference(r)
InitLicense(r)
+ // 404 on any api route before web.go has a chance to serve it
+ Srv.Router.Handle("/api/{anything:.*}", http.HandlerFunc(Handle404))
utils.InitHTML()
}
diff --git a/api/command_msg.go b/api/command_msg.go
new file mode 100644
index 000000000..273a45be9
--- /dev/null
+++ b/api/command_msg.go
@@ -0,0 +1,95 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package api
+
+import (
+ "github.com/mattermost/platform/model"
+ "strings"
+)
+
+type msgProvider struct {
+}
+
+const (
+ CMD_MSG = "msg"
+)
+
+func init() {
+ RegisterCommandProvider(&msgProvider{})
+}
+
+func (me *msgProvider) GetTrigger() string {
+ return CMD_MSG
+}
+
+func (me *msgProvider) GetCommand(c *Context) *model.Command {
+ return &model.Command{
+ Trigger: CMD_MSG,
+ AutoComplete: true,
+ AutoCompleteDesc: c.T("api.command_msg.desc"),
+ AutoCompleteHint: c.T("api.command_msg.hint"),
+ DisplayName: c.T("api.command_msg.name"),
+ }
+}
+
+func (me *msgProvider) DoCommand(c *Context, channelId string, message string) *model.CommandResponse {
+
+ splitMessage := strings.SplitN(message, " ", 2)
+
+ parsedMessage := ""
+ targetUser := ""
+
+ if len(splitMessage) > 1 {
+ parsedMessage = strings.SplitN(message, " ", 2)[1]
+ }
+ targetUser = strings.SplitN(message, " ", 2)[0]
+ targetUser = strings.TrimPrefix(targetUser, "@")
+
+ if profileList := <-Srv.Store.User().GetProfiles(c.Session.TeamId); profileList.Err != nil {
+ c.Err = profileList.Err
+ return &model.CommandResponse{Text: c.T("api.command_msg.list.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ } else {
+ profileUsers := profileList.Data.(map[string]*model.User)
+ for _, userProfile := range profileUsers {
+ //Don't let users open DMs with themselves. It probably won't work out well.
+ if userProfile.Id == c.Session.UserId {
+ continue
+ }
+ if userProfile.Username == targetUser {
+ targetChannelId := ""
+
+ //Find the channel based on this user
+ channelName := model.GetDMNameFromIds(c.Session.UserId, userProfile.Id)
+
+ if channel := <-Srv.Store.Channel().GetByName(c.Session.TeamId, channelName); channel.Err != nil {
+ if channel.Err.Id == "store.sql_channel.get_by_name.missing.app_error" {
+ if directChannel, err := CreateDirectChannel(c, userProfile.Id); err != nil {
+ c.Err = err
+ return &model.CommandResponse{Text: c.T("api.command_msg.dm_fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ } else {
+ targetChannelId = directChannel.Id
+ }
+ } else {
+ c.Err = channel.Err
+ return &model.CommandResponse{Text: c.T("api.command_msg.dm_fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+ } else {
+ targetChannelId = channel.Data.(*model.Channel).Id
+ }
+
+ makeDirectChannelVisible(c.Session.TeamId, targetChannelId)
+ if len(parsedMessage) > 0 {
+ post := &model.Post{}
+ post.Message = parsedMessage
+ post.ChannelId = targetChannelId
+ if _, err := CreatePost(c, post, true); err != nil {
+ return &model.CommandResponse{Text: c.T("api.command_msg.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+ }
+ return &model.CommandResponse{GotoLocation: c.GetTeamURL() + "/channels/" + channelName, Text: c.T("api.command_msg.success"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+ }
+ }
+ return &model.CommandResponse{Text: c.T("api.command_msg.missing.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+}
diff --git a/api/command_msg_test.go b/api/command_msg_test.go
new file mode 100644
index 000000000..222a401fd
--- /dev/null
+++ b/api/command_msg_test.go
@@ -0,0 +1,51 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package api
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/store"
+)
+
+func TestMsgCommands(t *testing.T) {
+ Setup()
+
+ team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "success+test@simulator.amazonses.com", Type: model.TEAM_OPEN}
+ team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
+
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Username: "user1", Password: "pwd"}
+ user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
+ store.Must(Srv.Store.User().VerifyEmail(user1.Id))
+
+ Client.LoginByEmail(team.Name, user1.Email, "pwd")
+
+ user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test2@simulator.amazonses.com", Nickname: "Corey Hulen 2", Username: "user2", Password: "pwd"}
+ user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
+ store.Must(Srv.Store.User().VerifyEmail(user2.Id))
+
+ user3 := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test3@simulator.amazonses.com", Nickname: "Corey Hulen 3", Username: "user3", Password: "pwd"}
+ user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User)
+ store.Must(Srv.Store.User().VerifyEmail(user3.Id))
+
+ rs1 := Client.Must(Client.Command("", "/msg user2", false)).Data.(*model.CommandResponse)
+ if !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user2.Id) && !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+user2.Id+"__"+user1.Id) {
+ t.Fatal("failed to create direct channel")
+ }
+
+ rs2 := Client.Must(Client.Command("", "/msg user3 foobar", false)).Data.(*model.CommandResponse)
+ if !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user3.Id) && !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+user3.Id+"__"+user1.Id) {
+ t.Fatal("failed to create second direct channel")
+ }
+ if result := Client.Must(Client.SearchPosts("foobar")).Data.(*model.PostList); len(result.Order) == 0 {
+ t.Fatalf("post did not get sent to direct message")
+ }
+
+ rs3 := Client.Must(Client.Command("", "/msg user2", false)).Data.(*model.CommandResponse)
+ if !strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user2.Id) && !strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+user2.Id+"__"+user1.Id) {
+ t.Fatal("failed to go back to existing direct channel")
+ }
+}
diff --git a/api/context.go b/api/context.go
index eed035daf..0f7ba0fff 100644
--- a/api/context.go
+++ b/api/context.go
@@ -476,25 +476,23 @@ func IsPrivateIpAddress(ipAddress string) bool {
}
func RenderWebError(err *model.AppError, w http.ResponseWriter, r *http.Request) {
- T, locale := utils.GetTranslationsAndLocale(w, r)
- page := utils.NewHTMLTemplate("error", locale)
- page.Props["Message"] = err.Message
- page.Props["Details"] = err.DetailedError
-
- pathParts := strings.Split(r.URL.Path, "/")
- if len(pathParts) > 1 {
- page.Props["SiteURL"] = GetProtocol(r) + "://" + r.Host + "/" + pathParts[1]
- } else {
- page.Props["SiteURL"] = GetProtocol(r) + "://" + r.Host
- }
-
- page.Props["Title"] = T("api.templates.error.title", map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
- page.Props["Link"] = T("api.templates.error.link")
-
- w.WriteHeader(err.StatusCode)
- if rErr := page.RenderToWriter(w); rErr != nil {
- l4g.Error("Failed to create error page: " + rErr.Error() + ", Original error: " + err.Error())
- }
+ T, _ := utils.GetTranslationsAndLocale(w, r)
+
+ title := T("api.templates.error.title", map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
+ message := err.Message
+ details := err.DetailedError
+ link := "/"
+ linkMessage := T("api.templates.error.link")
+
+ http.Redirect(
+ w,
+ r,
+ "/error?title="+url.QueryEscape(title)+
+ "&message="+url.QueryEscape(message)+
+ "&details="+url.QueryEscape(details)+
+ "&link="+url.QueryEscape(link)+
+ "&linkmessage="+url.QueryEscape(linkMessage),
+ http.StatusTemporaryRedirect)
}
func Handle404(w http.ResponseWriter, r *http.Request) {
diff --git a/i18n/en.json b/i18n/en.json
index 7dcc351f1..6292c1e03 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -316,6 +316,38 @@
"translation": "echo"
},
{
+ "id": "api.command_msg.desc",
+ "translation": "Send Direct Message to a user"
+ },
+ {
+ "id": "api.command_msg.fail.app_error",
+ "translation": "An error occured while messaging the user."
+ },
+ {
+ "id": "api.command_msg.dm_fail.app_error",
+ "translation": "An error occured while creating the direct message."
+ },
+ {
+ "id": "api.command_msg.hint",
+ "translation": "@[username] 'message'"
+ },
+ {
+ "id": "api.command_msg.list.app_error",
+ "translation": "An error occured while listing users."
+ },
+ {
+ "id": "api.command_msg.missing.app_error",
+ "translation": "We couldn't find the user"
+ },
+ {
+ "id": "api.command_msg.name",
+ "translation": "message"
+ },
+ {
+ "id": "api.command_msg.success",
+ "translation": "Messaged user."
+ },
+ {
"id": "api.command_join.desc",
"translation": "Join the open channel"
},
@@ -1296,14 +1328,6 @@
"translation": "You joined {{ .TeamDisplayName }}"
},
{
- "id": "api.user.update_mfa.not_available.app_error",
- "translation": "MFA not configured or available on this server"
- },
- {
- "id": "api.user.generate_mfa_qr.not_available.app_error",
- "translation": "MFA not configured or available on this server"
- },
- {
"id": "api.user.add_direct_channels_and_forget.failed.error",
"translation": "Failed to add direct channel preferences for user user_id=%s, team_id=%s, err=%v"
},
@@ -1340,16 +1364,16 @@
"translation": "Unsupported OAuth service provider"
},
{
- "id": "api.user.check_user_mfa.not_available.app_error",
- "translation": "MFA is not configured or supported on this server"
+ "id": "api.user.check_user_login_attempts.too_many.app_error",
+ "translation": "Your account is locked because of too many failed password attempts. Please reset your password."
},
{
"id": "api.user.check_user_mfa.bad_code.app_error",
"translation": "Invalid MFA token."
},
{
- "id": "api.user.check_user_login_attempts.too_many.app_error",
- "translation": "Your account is locked because of too many failed password attempts. Please reset your password."
+ "id": "api.user.check_user_mfa.not_available.app_error",
+ "translation": "MFA is not configured or supported on this server"
},
{
"id": "api.user.check_user_password.invalid.app_error",
@@ -1436,6 +1460,10 @@
"translation": "LDAP not available on this server"
},
{
+ "id": "api.user.generate_mfa_qr.not_available.app_error",
+ "translation": "MFA not configured or available on this server"
+ },
+ {
"id": "api.user.get_authorization_code.unsupported.app_error",
"translation": "Unsupported OAuth service provider"
},
@@ -1453,7 +1481,7 @@
},
{
"id": "api.user.ldap_to_email.not_ldap_account.app_error",
- "translation": "This user account does use LDAP"
+ "translation": "This user account does not use LDAP"
},
{
"id": "api.user.login.blank_pwd.app_error",
@@ -1580,6 +1608,10 @@
"translation": "You do not have the appropriate permissions"
},
{
+ "id": "api.user.update_mfa.not_available.app_error",
+ "translation": "MFA not configured or available on this server"
+ },
+ {
"id": "api.user.update_password.context.app_error",
"translation": "Update password failed because context user_id did not match props user_id"
},
@@ -1760,72 +1792,72 @@
"translation": "Compliance export started for job '{{.JobName}}' at '{{.FilePath}}'"
},
{
- "id": "ent.mfa.license_disable.app_error",
- "translation": "Your license does not support using multi-factor authentication"
+ "id": "ent.ldap.do_login.bind_admin_user.app_error",
+ "translation": "Unable to bind to LDAP server. Check BindUsername and BindPassword."
},
{
- "id": "ent.mfa.generate_qr_code.create_code.app_error",
- "translation": "Error generating QR code"
+ "id": "ent.ldap.do_login.invalid_password.app_error",
+ "translation": "Invalid Password"
},
{
- "id": "ent.mfa.generate_qr_code.save_secret.app_error",
- "translation": "Error saving the MFA secret"
+ "id": "ent.ldap.do_login.licence_disable.app_error",
+ "translation": "LDAP functionality disabled by current license. Please contact your system administrator about upgrading your enterprise license."
},
{
- "id": "ent.mfa.activate.authenticate.app_error",
- "translation": "Error attempting to authenticate MFA token"
+ "id": "ent.ldap.do_login.matched_to_many_users.app_error",
+ "translation": "Username given matches multiple users"
},
{
- "id": "ent.mfa.activate.bad_token.app_error",
- "translation": "Invalid MFA token"
+ "id": "ent.ldap.do_login.search_ldap_server.app_error",
+ "translation": "Failed to search LDAP server"
},
{
- "id": "ent.mfa.activate.save_active.app_erro",
- "translation": "Unable to update MFA active status for the user"
+ "id": "ent.ldap.do_login.unable_to_connect.app_error",
+ "translation": "Unable to connect to LDAP server"
},
{
- "id": "ent.mfa.deactivate.save_active.app_erro",
- "translation": "Unable to update MFA active status for the user"
+ "id": "ent.ldap.do_login.unable_to_create_user.app_error",
+ "translation": "Credentials valid but unable to create user."
},
{
- "id": "ent.mfa.deactivate.save_secret.app_error",
- "translation": "Error clearing the MFA secret"
+ "id": "ent.ldap.do_login.user_not_registered.app_error",
+ "translation": "User not registered on LDAP server"
},
{
- "id": "ent.mfa.validate_token.authenticate.app_error",
- "translation": "Error trying to authenticate MFA token"
+ "id": "ent.mfa.activate.authenticate.app_error",
+ "translation": "Error attempting to authenticate MFA token"
},
{
- "id": "ent.ldap.do_login.bind_admin_user.app_error",
- "translation": "Unable to bind to LDAP server. Check BindUsername and BindPassword."
+ "id": "ent.mfa.activate.bad_token.app_error",
+ "translation": "Invalid MFA token"
},
{
- "id": "ent.ldap.do_login.invalid_password.app_error",
- "translation": "Invalid Password"
+ "id": "ent.mfa.activate.save_active.app_erro",
+ "translation": "Unable to update MFA active status for the user"
},
{
- "id": "ent.ldap.do_login.licence_disable.app_error",
- "translation": "LDAP functionality disabled by current license. Please contact your system administrator about upgrading your enterprise license."
+ "id": "ent.mfa.deactivate.save_active.app_erro",
+ "translation": "Unable to update MFA active status for the user"
},
{
- "id": "ent.ldap.do_login.matched_to_many_users.app_error",
- "translation": "Username given matches multiple users"
+ "id": "ent.mfa.deactivate.save_secret.app_error",
+ "translation": "Error clearing the MFA secret"
},
{
- "id": "ent.ldap.do_login.search_ldap_server.app_error",
- "translation": "Failed to search LDAP server"
+ "id": "ent.mfa.generate_qr_code.create_code.app_error",
+ "translation": "Error generating QR code"
},
{
- "id": "ent.ldap.do_login.unable_to_connect.app_error",
- "translation": "Unable to connect to LDAP server"
+ "id": "ent.mfa.generate_qr_code.save_secret.app_error",
+ "translation": "Error saving the MFA secret"
},
{
- "id": "ent.ldap.do_login.unable_to_create_user.app_error",
- "translation": "Credentials valid but unable to create user."
+ "id": "ent.mfa.license_disable.app_error",
+ "translation": "Your license does not support using multi-factor authentication"
},
{
- "id": "ent.ldap.do_login.user_not_registered.app_error",
- "translation": "User not registered on LDAP server"
+ "id": "ent.mfa.validate_token.authenticate.app_error",
+ "translation": "Error trying to authenticate MFA token"
},
{
"id": "manaultesting.get_channel_id.no_found.debug",
@@ -2644,6 +2676,10 @@
"translation": "We couldn't find the existing channel"
},
{
+ "id": "store.sql_channel.get_by_name.missing.app_error",
+ "translation": "Channel does not exist"
+ },
+ {
"id": "store.sql_channel.get_channel_counts.get.app_error",
"translation": "We couldn't get the channel counts"
},
@@ -3192,14 +3228,6 @@
"translation": "We couldn't update the team name"
},
{
- "id": "store.sql_user.update_mfa_secret.app_error",
- "translation": "We encountered an error updating the user's MFA secret"
- },
- {
- "id": "store.sql_user.update_mfa_active.app_error",
- "translation": "We encountered an error updating the user's MFA active status"
- },
- {
"id": "store.sql_user.analytics_unique_user_count.app_error",
"translation": "We couldn't get the unique user count"
},
@@ -3320,6 +3348,14 @@
"translation": "We couldn't update the last_ping_at"
},
{
+ "id": "store.sql_user.update_mfa_active.app_error",
+ "translation": "We encountered an error updating the user's MFA active status"
+ },
+ {
+ "id": "store.sql_user.update_mfa_secret.app_error",
+ "translation": "We encountered an error updating the user's MFA secret"
+ },
+ {
"id": "store.sql_user.update_password.app_error",
"translation": "We couldn't update the user password"
},
@@ -3734,5 +3770,29 @@
{
"id": "web.watcher_fail.error",
"translation": "Failed to add directory to watcher %v"
+ },
+ {
+ "id": "error.not_found.title",
+ "translation": "Page not found"
+ },
+ {
+ "id": "error.not_found.message",
+ "translation": "The page you where trying to reach does not exist."
+ },
+ {
+ "id": "error.not_found.link_message",
+ "translation": "Back to Mattermost"
+ },
+ {
+ "id": "error.generic.title",
+ "translation": "Error"
+ },
+ {
+ "id": "error.generic.message",
+ "translation": "An error has occoured."
+ },
+ {
+ "id": "error.generic.link_message",
+ "translation": "Back to Mattermost"
}
]
diff --git a/i18n/es.json b/i18n/es.json
index 924ce59d2..52654f4af 100644
--- a/i18n/es.json
+++ b/i18n/es.json
@@ -476,6 +476,10 @@
"translation": "No se encontró el archivo."
},
{
+ "id": "api.file.get_file.public_disabled.app_error",
+ "translation": "Los enlaces públicos han sido deshabilitados por un administrador del sistema"
+ },
+ {
"id": "api.file.get_file.public_invalid.app_error",
"translation": "El enlace público parece ser inválido"
},
@@ -696,6 +700,14 @@
"translation": "Error obteniendo el token de acceso desde la BD antes de ser eliminado"
},
{
+ "id": "api.post.check_for_out_of_channel_mentions.message.multiple",
+ "translation": "{{.Usernames}} y {{.LastUsername}} fueron mencionados, pero no recibieron una notificación porque no pertenecen a este canal."
+ },
+ {
+ "id": "api.post.check_for_out_of_channel_mentions.message.one",
+ "translation": "{{.Username}} fue mencionado, pero no recibió una notificación porque no pertenece a este canal."
+ },
+ {
"id": "api.post.create_post.bad_filename.error",
"translation": "Nombre errado de archivo descartado, archivo=%v"
},
@@ -1324,6 +1336,14 @@
"translation": "Tu cuenta ha sido bloqueada debido a demasiados intentos fallidos. Por favor, restablece tu contraseña."
},
{
+ "id": "api.user.check_user_mfa.bad_code.app_error",
+ "translation": "Token AMF inválido."
+ },
+ {
+ "id": "api.user.check_user_mfa.not_available.app_error",
+ "translation": "AMF on está configurado o no es soportado en este servidor"
+ },
+ {
"id": "api.user.check_user_password.invalid.app_error",
"translation": "El inicio de sesión falló porque la contraseña es inválida"
},
@@ -1408,6 +1428,10 @@
"translation": "LDAP no está disponible en este servidor"
},
{
+ "id": "api.user.generate_mfa_qr.not_available.app_error",
+ "translation": "AMF no está configurado o disponible en este servidor"
+ },
+ {
"id": "api.user.get_authorization_code.unsupported.app_error",
"translation": "Proveedor de servicios de OAuth no es compatible"
},
@@ -1424,10 +1448,6 @@
"translation": "LDAP no está disponible en este servidor"
},
{
- "id": "api.user.ldap_to_email.not_ldap_account.app_error",
- "translation": "La cuenta de este usuario utiliza LDAP"
- },
- {
"id": "api.user.login.blank_pwd.app_error",
"translation": "El campo de contraseña no debe quedar en blanco"
},
@@ -1552,6 +1572,10 @@
"translation": "No tienes los permisos apropiados"
},
{
+ "id": "api.user.update_mfa.not_available.app_error",
+ "translation": "AMF no está configurado o disponible en este servidor"
+ },
+ {
"id": "api.user.update_password.context.app_error",
"translation": "La actualización de la contraseña falló debido a que el user_id del contexto no coincide con el user_id de los props"
},
@@ -1764,6 +1788,42 @@
"translation": "Usuario no registrado en el servidor LDAP"
},
{
+ "id": "ent.mfa.activate.authenticate.app_error",
+ "translation": "Error intentando autenticar el token AMF"
+ },
+ {
+ "id": "ent.mfa.activate.bad_token.app_error",
+ "translation": "Token AMF inválido"
+ },
+ {
+ "id": "ent.mfa.activate.save_active.app_erro",
+ "translation": "No se pudo actualizar el estado activo AMF para el usuario"
+ },
+ {
+ "id": "ent.mfa.deactivate.save_active.app_erro",
+ "translation": "No se pudo actualizar el estado activo AMF para el usuario"
+ },
+ {
+ "id": "ent.mfa.deactivate.save_secret.app_error",
+ "translation": "Error al limpiar el secreto AMF"
+ },
+ {
+ "id": "ent.mfa.generate_qr_code.create_code.app_error",
+ "translation": "Error generando el código QR"
+ },
+ {
+ "id": "ent.mfa.generate_qr_code.save_secret.app_error",
+ "translation": "Error guardando el secreto AMF"
+ },
+ {
+ "id": "ent.mfa.license_disable.app_error",
+ "translation": "Tu licencia no soporta la autenticación de múltiples factores"
+ },
+ {
+ "id": "ent.mfa.validate_token.authenticate.app_error",
+ "translation": "Error intentando autenticar el token AMF"
+ },
+ {
"id": "manaultesting.get_channel_id.no_found.debug",
"translation": "No pudimos encontrar el canal: %v, búsqueda realizada con estas posibilidades %v"
},
@@ -3248,6 +3308,14 @@
"translation": "No pudimos actualizar el campo last_ping_at"
},
{
+ "id": "store.sql_user.update_mfa_active.app_error",
+ "translation": "Encontramos un error al actualizar el estado activo AMF del usuario"
+ },
+ {
+ "id": "store.sql_user.update_mfa_secret.app_error",
+ "translation": "Encontramos un error al actualizar el secreto AMF del usuario"
+ },
+ {
"id": "store.sql_user.update_password.app_error",
"translation": "No pudimos actualizar la contraseña del usuario"
},
diff --git a/i18n/fr.json b/i18n/fr.json
index c52e17af8..984c1ef93 100644
--- a/i18n/fr.json
+++ b/i18n/fr.json
@@ -1540,10 +1540,6 @@
"translation": "LDAP n'est pas disponible sur ce serveur"
},
{
- "id": "api.user.ldap_to_email.not_ldap_account.app_error",
- "translation": "Ce compte utilisateur utilise LDAP"
- },
- {
"id": "api.user.ldap_to_email.not_available.app_error",
"translation": "LDAP n'est pas disponible sur ce serveur"
},
diff --git a/i18n/pt.json b/i18n/pt.json
index 11ab26d6c..631198e1f 100644
--- a/i18n/pt.json
+++ b/i18n/pt.json
@@ -476,6 +476,10 @@
"translation": "Não foi possível encontrar o arquivo."
},
{
+ "id": "api.file.get_file.public_disabled.app_error",
+ "translation": "Public links have been disabled by the system administrator"
+ },
+ {
"id": "api.file.get_file.public_invalid.app_error",
"translation": "O link público não parece ser válido"
},
@@ -696,6 +700,14 @@
"translation": "Erro ao obter o token de acesso do BD antes da exclusão"
},
{
+ "id": "api.post.check_for_out_of_channel_mentions.message.multiple",
+ "translation": "{{.Usernames}} e {{.LastUsername}} foram mencionados, mas eles não receberam notificação porque eles não pertencem a este canal."
+ },
+ {
+ "id": "api.post.check_for_out_of_channel_mentions.message.one",
+ "translation": "{{.Username}} foi mencionado, mas eles não receberam uma notificação porque eles não pertencem a este canal."
+ },
+ {
"id": "api.post.create_post.bad_filename.error",
"translation": "Nome ruim do arquivo descartado, nome do arquivo=%v"
},
@@ -1284,6 +1296,14 @@
"translation": "Você se juntou {{ .TeamDisplayName }}"
},
{
+ "id": "api.user.update_mfa.not_available.app_error",
+ "translation": "MFA não configurado ou disponível neste servidor"
+ },
+ {
+ "id": "api.user.generate_mfa_qr.not_available.app_error",
+ "translation": "MFA não configurado ou disponível neste servidor"
+ },
+ {
"id": "api.user.add_direct_channels_and_forget.failed.error",
"translation": "Falha ao adicionar preferencias diretas ao canal para o usuário user_id=%s, team_id=%s, err=%v"
},
@@ -1320,6 +1340,14 @@
"translation": "Provedor de serviço OAuth não suportado"
},
{
+ "id": "api.user.check_user_mfa.not_available.app_error",
+ "translation": "MFA não configurado ou disponível neste servidor"
+ },
+ {
+ "id": "api.user.check_user_mfa.bad_code.app_error",
+ "translation": "Token MFA inválido."
+ },
+ {
"id": "api.user.check_user_login_attempts.too_many.app_error",
"translation": "A sua conta está bloqueada por causa de muitas tentativas de senha que falharam. Por favor, redefina sua senha."
},
@@ -1424,10 +1452,6 @@
"translation": "LDAP não está disponível neste servidor"
},
{
- "id": "api.user.ldap_to_email.not_ldap_account.app_error",
- "translation": "Está conta de usuário não utiliza LDAP"
- },
- {
"id": "api.user.login.blank_pwd.app_error",
"translation": "Campo senha não pode estar em branco"
},
@@ -1732,6 +1756,42 @@
"translation": "Exportação de compliance tarefa '{{.JobName}}' iniciada no '{{.FilePath}}'"
},
{
+ "id": "ent.mfa.license_disable.app_error",
+ "translation": "Sua licença não suporta o uso de autenticação multi-fator"
+ },
+ {
+ "id": "ent.mfa.generate_qr_code.create_code.app_error",
+ "translation": "Erro ao gerar QR code"
+ },
+ {
+ "id": "ent.mfa.generate_qr_code.save_secret.app_error",
+ "translation": "Erro ao salvar o segredo MFA"
+ },
+ {
+ "id": "ent.mfa.activate.authenticate.app_error",
+ "translation": "Erro ao tentar autenticar o token MFA"
+ },
+ {
+ "id": "ent.mfa.activate.bad_token.app_error",
+ "translation": "Token MFA inválido"
+ },
+ {
+ "id": "ent.mfa.activate.save_active.app_erro",
+ "translation": "Não foi possível atualizar o status ativo MFA para o usuário"
+ },
+ {
+ "id": "ent.mfa.deactivate.save_active.app_erro",
+ "translation": "Não foi possível atualizar o status ativo MFA para o usuário"
+ },
+ {
+ "id": "ent.mfa.deactivate.save_secret.app_error",
+ "translation": "Erro ao limpar o segredo MFA"
+ },
+ {
+ "id": "ent.mfa.validate_token.authenticate.app_error",
+ "translation": "Erro ao tentar autenticar o token MFA"
+ },
+ {
"id": "ent.ldap.do_login.bind_admin_user.app_error",
"translation": "Não foi possível ligar ao servidor LDAP. Verifique BindUsername e BindPassword."
},
@@ -3128,6 +3188,14 @@
"translation": "Não foi possível atualizar o nome da equipe"
},
{
+ "id": "store.sql_user.update_mfa_secret.app_error",
+ "translation": "Foi encontrado um erro ao atualizar o segredo MFA do usuário"
+ },
+ {
+ "id": "store.sql_user.update_mfa_active.app_error",
+ "translation": "Encontramos um erro ao atualizar o status ativo MFA do usuário"
+ },
+ {
"id": "store.sql_user.analytics_unique_user_count.app_error",
"translation": "Não foi possível obter o número de usuários únicos"
},
@@ -3663,4 +3731,4 @@
"id": "web.watcher_fail.error",
"translation": "Falha ao adicionar diretório observador %v"
}
-]
+] \ No newline at end of file
diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go
index 35322e061..c7ffddd56 100644
--- a/store/sql_channel_store.go
+++ b/store/sql_channel_store.go
@@ -4,11 +4,16 @@
package store
import (
+ "database/sql"
"github.com/go-gorp/gorp"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
)
+const (
+ MISSING_CHANNEL_ERROR = "store.sql_channel.get_by_name.missing.app_error"
+)
+
type SqlChannelStore struct {
*SqlStore
}
@@ -437,7 +442,11 @@ func (s SqlChannelStore) GetByName(teamId string, name string) StoreChannel {
channel := model.Channel{}
if err := s.GetReplica().SelectOne(&channel, "SELECT * FROM Channels WHERE TeamId = :TeamId AND Name= :Name AND DeleteAt = 0", map[string]interface{}{"TeamId": teamId, "Name": name}); err != nil {
- result.Err = model.NewLocAppError("SqlChannelStore.GetByName", "store.sql_channel.get_by_name.existing.app_error", nil, "teamId="+teamId+", "+"name="+name+", "+err.Error())
+ if err == sql.ErrNoRows {
+ result.Err = model.NewLocAppError("SqlChannelStore.GetByName", MISSING_CHANNEL_ERROR, nil, "teamId="+teamId+", "+"name="+name+", "+err.Error())
+ } else {
+ result.Err = model.NewLocAppError("SqlChannelStore.GetByName", "store.sql_channel.get_by_name.existing.app_error", nil, "teamId="+teamId+", "+"name="+name+", "+err.Error())
+ }
} else {
result.Data = &channel
}
diff --git a/templates/error.html b/templates/error.html
deleted file mode 100644
index 5aa48098f..000000000
--- a/templates/error.html
+++ /dev/null
@@ -1,24 +0,0 @@
-{{define "error"}}
-<!DOCTYPE html>
-<html>
-{{template "head" . }}
-<body class="sticky error">
- <div class="container-fluid">
- <div class="error__container">
- <div class="error__icon">
- <i class="fa fa-exclamation-triangle"/>
- </div>
- <h2>{{.Props.Title}}</h2>
- <p>{{ .Props.Message }}</p>
- <a href="{{.Props.SiteURL}}">{{.Props.Link}}</a>
- </div>
- </div>
-</body>
-<script>
-var details = {{ .Props.Details }};
-if (details.length > 0) {
- console.log("error details: " + details);
-}
-</script>
-</html>
-{{end}}
diff --git a/webapp/components/about_build_modal.jsx b/webapp/components/about_build_modal.jsx
index a47225f7e..4fd946401 100644
--- a/webapp/components/about_build_modal.jsx
+++ b/webapp/components/about_build_modal.jsx
@@ -6,6 +6,7 @@ import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import React from 'react';
+import Constants from 'utils/constants.jsx';
export default class AboutBuildModal extends React.Component {
constructor(props) {
@@ -20,6 +21,7 @@ export default class AboutBuildModal extends React.Component {
render() {
const config = global.window.mm_config;
const license = global.window.mm_license;
+ const mattermostLogo = Constants.MATTERMOST_ICON_SVG;
let title = (
<FormattedMessage
@@ -28,6 +30,28 @@ export default class AboutBuildModal extends React.Component {
/>
);
+ let subTitle = (
+ <FormattedMessage
+ id='about.teamEditionSt'
+ defaultMessage='All your team communication in one place, instantly searchable and accessible anywhere.'
+ />
+ );
+
+ let learnMore = (
+ <div>
+ <FormattedMessage
+ id='about.teamEditionLearn'
+ defaultMessage='Join the Mattermost community at '
+ />
+ <a
+ target='_blank'
+ href='http://www.mattermost.org/'
+ >
+ {'mattermost.org'}
+ </a>
+ </div>
+ );
+
let licensee;
if (config.BuildEnterpriseReady === 'true') {
title = (
@@ -36,6 +60,29 @@ export default class AboutBuildModal extends React.Component {
defaultMessage='Enterprise Edition'
/>
);
+
+ subTitle = (
+ <FormattedMessage
+ id='about.enterpriseEditionSt'
+ defaultMessage='Modern enterprise communication from behind your firewall.'
+ />
+ );
+
+ learnMore = (
+ <div>
+ <FormattedMessage
+ id='about.enterpriseEditionLearn'
+ defaultMessage='Learn more about Enterprise Edition at '
+ />
+ <a
+ target='_blank'
+ href='http://about.mattermost.com/'
+ >
+ {'about.mattermost.com'}
+ </a>
+ </div>
+ );
+
if (license.IsLicensed === 'true') {
title = (
<FormattedMessage
@@ -44,14 +91,12 @@ export default class AboutBuildModal extends React.Component {
/>
);
licensee = (
- <div className='row form-group'>
- <div className='col-sm-3 info__label'>
- <FormattedMessage
- id='about.licensed'
- defaultMessage='Licensed by:'
- />
- </div>
- <div className='col-sm-9'>{license.Company}</div>
+ <div className='form-group'>
+ <FormattedMessage
+ id='about.licensed'
+ defaultMessage='Licensed by:'
+ />
+ &nbsp;{license.Company}
</div>
);
}
@@ -59,6 +104,7 @@ export default class AboutBuildModal extends React.Component {
return (
<Modal
+ dialogClassName='about-modal'
show={this.props.show}
onHide={this.doHide}
>
@@ -71,57 +117,54 @@ export default class AboutBuildModal extends React.Component {
</Modal.Title>
</Modal.Header>
<Modal.Body>
- <h4 className='padding-bottom x2'>{'Mattermost'} {title}</h4>
- {licensee}
- <div className='row form-group'>
- <div className='col-sm-3 info__label'>
- <FormattedMessage
- id='about.version'
- defaultMessage='Version:'
+ <div className='about-modal__content'>
+ <div className='about-modal__logo'>
+ <span
+ className='icon'
+ dangerouslySetInnerHTML={{__html: mattermostLogo}}
/>
</div>
- <div className='col-sm-9'>{config.Version}</div>
- </div>
- <div className='row form-group'>
- <div className='col-sm-3 info__label'>
- <FormattedMessage
- id='about.number'
- defaultMessage='Build Number:'
- />
+ <div>
+ <h3 className='about-modal__title'>{'Mattermost'} {title}</h3>
+ <p className='about-modal__subtitle padding-bottom'>{subTitle}</p>
+ <div className='form-group less'>
+ <div>
+ <FormattedMessage
+ id='about.version'
+ defaultMessage='Version:'
+ />
+ &nbsp;{config.Version}&nbsp;({config.BuildNumber})
+ </div>
+ </div>
+ {licensee}
</div>
- <div className='col-sm-9'>{config.BuildNumber}</div>
</div>
- <div className='row form-group'>
- <div className='col-sm-3 info__label'>
+ <div className='about-modal__footer'>
+ {learnMore}
+ <div className='form-group about-modal__copyright'>
<FormattedMessage
- id='about.date'
- defaultMessage='Build Date:'
+ id='about.copyright'
+ defaultMessage='Copyright 2016 Mattermost, Inc. All rights reserved'
/>
</div>
- <div className='col-sm-9'>{config.BuildDate}</div>
</div>
- <div className='row form-group'>
- <div className='col-sm-3 info__label'>
+ <div className='about-modal__hash form-group padding-top x2'>
+ <p>
<FormattedMessage
id='about.hash'
defaultMessage='Build Hash:'
/>
- </div>
- <div className='col-sm-9'>{config.BuildHash}</div>
+ &nbsp;{config.BuildHash}
+ </p>
+ <p>
+ <FormattedMessage
+ id='about.date'
+ defaultMessage='Build Date:'
+ />
+ &nbsp;{config.BuildDate}
+ </p>
</div>
</Modal.Body>
- <Modal.Footer>
- <button
- type='button'
- className='btn btn-default'
- onClick={this.doHide}
- >
- <FormattedMessage
- id='about.close'
- defaultMessage='Close'
- />
- </button>
- </Modal.Footer>
</Modal>
);
}
diff --git a/webapp/components/admin_console/compliance_settings.jsx b/webapp/components/admin_console/compliance_settings.jsx
index fb2ae26f9..206bb0faa 100644
--- a/webapp/components/admin_console/compliance_settings.jsx
+++ b/webapp/components/admin_console/compliance_settings.jsx
@@ -223,7 +223,7 @@ export default class ComplianceSettings extends React.Component {
</label>
<p className='help-text'>
<FormattedMessage
- id='admin.compliance.enableDesc'
+ id='admin.compliance.enableDailyDesc'
defaultMessage='When true, Mattermost will generate a daily compliance report.'
/>
</p>
diff --git a/webapp/components/admin_console/service_settings.jsx b/webapp/components/admin_console/service_settings.jsx
index c72c97326..2c3f4081c 100644
--- a/webapp/components/admin_console/service_settings.jsx
+++ b/webapp/components/admin_console/service_settings.jsx
@@ -87,7 +87,7 @@ class ServiceSettings extends React.Component {
config.ServiceSettings.EnableCommands = ReactDOM.findDOMNode(this.refs.EnableCommands).checked;
config.ServiceSettings.EnableOnlyAdminIntegrations = ReactDOM.findDOMNode(this.refs.EnableOnlyAdminIntegrations).checked;
- if (this.refs.EnablMultifactorAuthentication) {
+ if (this.refs.EnableMultifactorAuthentication) {
config.ServiceSettings.EnableMultifactorAuthentication = ReactDOM.findDOMNode(this.refs.EnableMultifactorAuthentication).checked;
}
diff --git a/webapp/components/admin_console/user_item.jsx b/webapp/components/admin_console/user_item.jsx
index 91f567d4d..c00050584 100644
--- a/webapp/components/admin_console/user_item.jsx
+++ b/webapp/components/admin_console/user_item.jsx
@@ -333,7 +333,7 @@ export default class UserItem extends React.Component {
<div>
<FormattedMessage
id='admin.user_item.confirmDemoteDescription'
- defaultMessage="If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you\'ll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command."
+ defaultMessage="If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you'll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command."
/>
<br/>
<br/>
diff --git a/webapp/components/analytics/team_analytics.jsx b/webapp/components/analytics/team_analytics.jsx
index efc965f24..9b4eb1f94 100644
--- a/webapp/components/analytics/team_analytics.jsx
+++ b/webapp/components/analytics/team_analytics.jsx
@@ -154,7 +154,7 @@ class TeamAnalytics extends React.Component {
<TableChart
title={
<FormattedMessage
- id='analytics.team.activeUsers'
+ id='analytics.team.recentUsers'
defaultMessage='Recent Active Users'
/>
}
diff --git a/webapp/components/backstage/add_incoming_webhook.jsx b/webapp/components/backstage/add_incoming_webhook.jsx
index fa7531fc6..83027c6b3 100644
--- a/webapp/components/backstage/add_incoming_webhook.jsx
+++ b/webapp/components/backstage/add_incoming_webhook.jsx
@@ -96,10 +96,10 @@ export default class AddIncomingWebhook extends React.Component {
render() {
return (
- <div className='backstage row'>
+ <div className='backstage-content row'>
<div className='add-incoming-webhook'>
- <div className='backstage__header'>
- <h1 className='text'>
+ <div className='backstage-header'>
+ <h1>
<FormattedMessage
id='add_incoming_webhook.header'
defaultMessage='Add Incoming Webhook'
@@ -107,81 +107,91 @@ export default class AddIncomingWebhook extends React.Component {
</h1>
</div>
</div>
- <form className='add-incoming-webhook__body'>
- <div className='add-integration__row'>
- <label
- className='add-integration__label'
- htmlFor='name'
- >
- <FormattedMessage
- id='add_incoming_webhook.name'
- defaultMessage='Name'
- />
- </label>
- <input
- id='name'
- type='text'
- value={this.state.name}
- onChange={this.updateName}
- />
- </div>
- <div className='add-integration__row'>
- <label
- className='add-integration__label'
- htmlFor='description'
- >
- <FormattedMessage
- id='add_incoming_webhook.description'
- defaultMessage='Description'
- />
- </label>
- <input
- id='description'
- type='text'
- value={this.state.description}
- onChange={this.updateDescription}
- />
- </div>
- <div className='add-integration__row'>
- <label
- className='add-integration__label'
- htmlFor='channelId'
- >
- <FormattedMessage
- id='add_incoming_webhook.channel'
- defaultMessage='Channel'
- />
- </label>
- <ChannelSelect
- id='channelId'
- value={this.state.channelId}
- onChange={this.updateChannelId}
- />
- </div>
- <div className='add-integration__submit-row'>
- <Link
- className='btn btn-sm'
- to={'/settings/integrations/add'}
- >
- <FormattedMessage
- id='add_incoming_webhook.cancel'
- defaultMessage='Cancel'
- />
- </Link>
- <SpinnerButton
- className='btn btn-primary'
- type='submit'
- spinning={this.state.saving}
- onClick={this.handleSubmit}
- >
- <FormattedMessage
- id='add_incoming_webhook.save'
- defaultMessage='Save'
- />
- </SpinnerButton>
- </div>
- <FormError errors={[this.state.serverError, this.state.clientError]}/>
- </form>
+ <div className='backstage-form'>
+ <form className='form-horizontal'>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-3'
+ htmlFor='name'
+ >
+ <FormattedMessage
+ id='add_incoming_webhook.name'
+ defaultMessage='Name'
+ />
+ </label>
+ <div className='col-md-5 col-sm-9'>
+ <input
+ id='name'
+ type='text'
+ className='form-control'
+ value={this.state.name}
+ onChange={this.updateName}
+ />
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-3'
+ htmlFor='description'
+ >
+ <FormattedMessage
+ id='add_incoming_webhook.description'
+ defaultMessage='Description'
+ />
+ </label>
+ <div className='col-md-5 col-sm-9'>
+ <input
+ id='description'
+ type='text'
+ className='form-control'
+ value={this.state.description}
+ onChange={this.updateDescription}
+ />
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-3'
+ htmlFor='channelId'
+ >
+ <FormattedMessage
+ id='add_incoming_webhook.channel'
+ defaultMessage='Channel'
+ />
+ </label>
+ <div className='col-md-5 col-sm-9'>
+ <ChannelSelect
+ id='channelId'
+ value={this.state.channelId}
+ onChange={this.updateChannelId}
+ />
+ </div>
+ </div>
+ <div className='backstage-form__footer'>
+ <FormError errors={[this.state.serverError, this.state.clientError]}/>
+ <Link
+ className='btn btn-sm'
+ to={'/settings/integrations/add'}
+ >
+ <FormattedMessage
+ id='add_incoming_webhook.cancel'
+ defaultMessage='Cancel'
+ />
+ </Link>
+ <SpinnerButton
+ className='btn btn-primary'
+ type='submit'
+ spinning={this.state.saving}
+ onClick={this.handleSubmit}
+ >
+ <FormattedMessage
+ id='add_incoming_webhook.save'
+ defaultMessage='Save'
+ />
+ </SpinnerButton>
+ </div>
+ </form>
+ </div>
</div>
);
}
diff --git a/webapp/components/backstage/add_integration.jsx b/webapp/components/backstage/add_integration.jsx
index cebc1e8b0..5f4a69bfe 100644
--- a/webapp/components/backstage/add_integration.jsx
+++ b/webapp/components/backstage/add_integration.jsx
@@ -57,18 +57,16 @@ export default class AddIntegration extends React.Component {
}
return (
- <div className='backstage row'>
- <div className='add-integration'>
- <div className='backstage__header'>
- <h1 className='text'>
- <FormattedMessage
- id='add_integration.header'
- defaultMessage='Add Integration'
- />
- </h1>
- </div>
+ <div className='backstage-content row'>
+ <div className='backstage-header'>
+ <h1>
+ <FormattedMessage
+ id='add_integration.header'
+ defaultMessage='Add Integration'
+ />
+ </h1>
</div>
- <div className='add-integration__options'>
+ <div>
{options}
</div>
</div>
diff --git a/webapp/components/backstage/add_integration_option.jsx b/webapp/components/backstage/add_integration_option.jsx
index 3c3caf2f4..b17ebb185 100644
--- a/webapp/components/backstage/add_integration_option.jsx
+++ b/webapp/components/backstage/add_integration_option.jsx
@@ -21,16 +21,16 @@ export default class AddIntegrationOption extends React.Component {
return (
<Link
to={link}
- className='add-integration-option'
+ className='add-integration'
>
<img
- className='add-integration-option__image'
+ className='add-integration__image'
src={image}
/>
- <div className='add-integration-option__title'>
+ <div className='add-integration__title'>
{title}
</div>
- <div className='add-integration-option__description'>
+ <div className='add-integration__description'>
{description}
</div>
</Link>
diff --git a/webapp/components/backstage/add_outgoing_webhook.jsx b/webapp/components/backstage/add_outgoing_webhook.jsx
index 3ae2f8606..5d98138df 100644
--- a/webapp/components/backstage/add_outgoing_webhook.jsx
+++ b/webapp/components/backstage/add_outgoing_webhook.jsx
@@ -128,10 +128,10 @@ export default class AddOutgoingWebhook extends React.Component {
render() {
return (
- <div className='backstage row'>
+ <div className='backstage-content row'>
<div className='add-outgoing-webhook'>
- <div className='backstage__header'>
- <h1 className='text'>
+ <div className='backstage-header'>
+ <h1>
<FormattedMessage
id='add_outgoing_webhook.header'
defaultMessage='Add Outgoing Webhook'
@@ -139,115 +139,131 @@ export default class AddOutgoingWebhook extends React.Component {
</h1>
</div>
</div>
- <form className='add-outgoing-webhook__body'>
- <div className='add-integration__row'>
- <label
- className='add-integration__label'
- htmlFor='name'
- >
- <FormattedMessage
- id='add_outgoing_webhook.name'
- defaultMessage='Name'
- />
- </label>
- <input
- id='name'
- type='text'
- value={this.state.name}
- onChange={this.updateName}
- />
- </div>
- <div className='add-integration__row'>
- <label
- className='add-integration__label'
- htmlFor='description'
- >
- <FormattedMessage
- id='add_outgoing_webhook.description'
- defaultMessage='Description'
- />
- </label>
- <input
- id='description'
- type='text'
- value={this.state.description}
- onChange={this.updateDescription}
- />
- </div>
- <div className='add-integration__row'>
- <label
- className='add-integration__label'
- htmlFor='channelId'
- >
- <FormattedMessage
- id='add_outgoing_webhook.channel'
- defaultMessage='Channel'
- />
- </label>
- <ChannelSelect
- id='channelId'
- value={this.state.channelId}
- onChange={this.updateChannelId}
- />
- </div>
- <div className='add-integration__row'>
- <label
- className='add-integration__label'
- htmlFor='triggerWords'
- >
- <FormattedMessage
- id='add_outgoing_webhook.triggerWords'
- defaultMessage='Trigger Words (One Per Line)'
- />
- </label>
- <textarea
- id='triggerWords'
- rows='3'
- value={this.state.triggerWords}
- onChange={this.updateTriggerWords}
- />
- </div>
- <div className='add-integration__row'>
- <label
- className='add-integration__label'
- htmlFor='callbackUrls'
- >
- <FormattedMessage
- id='add_outgoing_webhook.callbackUrls'
- defaultMessage='Callback URLs (One Per Line)'
- />
- </label>
- <textarea
- id='callbackUrls'
- rows='3'
- value={this.state.callbackUrls}
- onChange={this.updateCallbackUrls}
- />
- </div>
- <div className='add-integration__submit-row'>
- <Link
- className='btn btn-sm'
- to={'/settings/integrations/add'}
- >
- <FormattedMessage
- id='add_outgoing_webhook.cancel'
- defaultMessage='Cancel'
- />
- </Link>
- <SpinnerButton
- className='btn btn-primary'
- type='submit'
- spinning={this.state.saving}
- onClick={this.handleSubmit}
- >
- <FormattedMessage
- id='add_outgoing_webhook.save'
- defaultMessage='Save'
- />
- </SpinnerButton>
- </div>
- <FormError errors={[this.state.serverError, this.state.clientError]}/>
- </form>
+ <div className='backstage-form'>
+ <form className='form-horizontal'>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-3'
+ htmlFor='name'
+ >
+ <FormattedMessage
+ id='add_outgoing_webhook.name'
+ defaultMessage='Name'
+ />
+ </label>
+ <div className='col-md-5 col-sm-9'>
+ <input
+ id='name'
+ type='text'
+ className='form-control'
+ value={this.state.name}
+ onChange={this.updateName}
+ />
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-3'
+ htmlFor='description'
+ >
+ <FormattedMessage
+ id='add_outgoing_webhook.description'
+ defaultMessage='Description'
+ />
+ </label>
+ <div className='col-md-5 col-sm-9'>
+ <input
+ id='description'
+ type='text'
+ className='form-control'
+ value={this.state.description}
+ onChange={this.updateDescription}
+ />
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-3'
+ htmlFor='channelId'
+ >
+ <FormattedMessage
+ id='add_outgoing_webhook.channel'
+ defaultMessage='Channel'
+ />
+ </label>
+ <div className='col-md-5 col-sm-9'>
+ <ChannelSelect
+ id='channelId'
+ value={this.state.channelId}
+ onChange={this.updateChannelId}
+ />
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-3'
+ htmlFor='triggerWords'
+ >
+ <FormattedMessage
+ id='add_outgoing_webhook.triggerWords'
+ defaultMessage='Trigger Words (One Per Line)'
+ />
+ </label>
+ <div className='col-md-5 col-sm-9'>
+ <textarea
+ id='triggerWords'
+ rows='3'
+ className='form-control'
+ value={this.state.triggerWords}
+ onChange={this.updateTriggerWords}
+ />
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-3'
+ htmlFor='callbackUrls'
+ >
+ <FormattedMessage
+ id='add_outgoing_webhook.callbackUrls'
+ defaultMessage='Callback URLs (One Per Line)'
+ />
+ </label>
+ <div className='col-md-5 col-sm-9'>
+ <textarea
+ id='callbackUrls'
+ rows='3'
+ className='form-control'
+ value={this.state.callbackUrls}
+ onChange={this.updateCallbackUrls}
+ />
+ </div>
+ </div>
+ <div className='backstage-form__footer'>
+ <FormError errors={[this.state.serverError, this.state.clientError]}/>
+ <Link
+ className='btn btn-sm'
+ to={'/settings/integrations/add'}
+ >
+ <FormattedMessage
+ id='add_outgoing_webhook.cancel'
+ defaultMessage='Cancel'
+ />
+ </Link>
+ <SpinnerButton
+ className='btn btn-primary'
+ type='submit'
+ spinning={this.state.saving}
+ onClick={this.handleSubmit}
+ >
+ <FormattedMessage
+ id='add_outgoing_webhook.save'
+ defaultMessage='Save'
+ />
+ </SpinnerButton>
+ </div>
+ </form>
+ </div>
</div>
);
}
diff --git a/webapp/components/backstage/backstage_category.jsx b/webapp/components/backstage/backstage_category.jsx
index e8b0b57ae..913c7562c 100644
--- a/webapp/components/backstage/backstage_category.jsx
+++ b/webapp/components/backstage/backstage_category.jsx
@@ -50,7 +50,7 @@ export default class BackstageCategory extends React.Component {
}
return (
- <li className='backstage__sidebar__category'>
+ <li className='backstage-sidebar__category'>
<Link
to={link}
className='category-title'
diff --git a/webapp/components/backstage/backstage_navbar.jsx b/webapp/components/backstage/backstage_navbar.jsx
index 555165791..d1dac6043 100644
--- a/webapp/components/backstage/backstage_navbar.jsx
+++ b/webapp/components/backstage/backstage_navbar.jsx
@@ -39,9 +39,9 @@ export default class BackstageNavbar extends React.Component {
}
return (
- <div className='backstage__navbar row'>
+ <div className='backstage-navbar row'>
<Link
- className='backstage__navbar__back'
+ className='backstage-navbar__back'
to={`/${this.state.team.display_name}/channels/town-square`}
>
<i className='fa fa-angle-left'/>
diff --git a/webapp/components/backstage/backstage_sidebar.jsx b/webapp/components/backstage/backstage_sidebar.jsx
index 63a0df5cb..13c4f8b50 100644
--- a/webapp/components/backstage/backstage_sidebar.jsx
+++ b/webapp/components/backstage/backstage_sidebar.jsx
@@ -10,7 +10,7 @@ import {FormattedMessage} from 'react-intl';
export default class BackstageSidebar extends React.Component {
render() {
return (
- <div className='backstage__sidebar'>
+ <div className='backstage-sidebar'>
<ul>
<BackstageCategory
name='integrations'
diff --git a/webapp/components/backstage/installed_incoming_webhook.jsx b/webapp/components/backstage/installed_incoming_webhook.jsx
index 4ca421a02..f65cf6327 100644
--- a/webapp/components/backstage/installed_incoming_webhook.jsx
+++ b/webapp/components/backstage/installed_incoming_webhook.jsx
@@ -35,26 +35,26 @@ export default class InstalledIncomingWebhook extends React.Component {
const channelName = channel ? channel.display_name : 'cannot find channel';
return (
- <div className='installed-integrations__item installed-integrations__incoming-webhook'>
- <div className='details'>
- <div className='details-row'>
- <span className='name'>
+ <div className='backstage-list__item'>
+ <div className='item-details'>
+ <div className='item-details__row'>
+ <span className='item-details__name'>
{channelName}
</span>
- <span className='type'>
+ <span className='item-details__type'>
<FormattedMessage
id='installed_integrations.incomingWebhookType'
defaultMessage='(Incoming Webhook)'
/>
</span>
</div>
- <div className='details-row'>
- <span className='description'>
+ <div className='item-details__row'>
+ <span className='item-details__description'>
{Utils.getWindowLocationOrigin() + '/hooks/' + incomingWebhook.id}
</span>
</div>
</div>
- <div className='actions'>
+ <div className='item-actions'>
<a
href='#'
onClick={this.handleDeleteClick}
diff --git a/webapp/components/backstage/installed_integrations.jsx b/webapp/components/backstage/installed_integrations.jsx
index ff0b6e4ec..fe84ae81a 100644
--- a/webapp/components/backstage/installed_integrations.jsx
+++ b/webapp/components/backstage/installed_integrations.jsx
@@ -98,9 +98,9 @@ export default class InstalledIntegrations extends React.Component {
const fields = [];
if (incomingWebhooks.length > 0 || outgoingWebhooks.length > 0) {
- let filterClassName = 'type-filter';
+ let filterClassName = 'filter-sort';
if (this.state.typeFilter === '') {
- filterClassName += ' type-filter--selected';
+ filterClassName += ' filter-sort--active';
}
fields.push(
@@ -131,9 +131,9 @@ export default class InstalledIntegrations extends React.Component {
</span>
);
- let filterClassName = 'type-filter';
+ let filterClassName = 'filter-sort';
if (this.state.typeFilter === 'incomingWebhooks') {
- filterClassName += ' type-filter--selected';
+ filterClassName += ' filter-sort--active';
}
fields.push(
@@ -164,9 +164,9 @@ export default class InstalledIntegrations extends React.Component {
</span>
);
- let filterClassName = 'type-filter';
+ let filterClassName = 'filter-sort';
if (this.state.typeFilter === 'outgoingWebhooks') {
- filterClassName += ' type-filter--selected';
+ filterClassName += ' filter-sort--active';
}
fields.push(
@@ -188,7 +188,7 @@ export default class InstalledIntegrations extends React.Component {
}
return (
- <div className='type-filters'>
+ <div className='backstage-filters__sort'>
{fields}
</div>
);
@@ -243,10 +243,10 @@ export default class InstalledIntegrations extends React.Component {
}
return (
- <div className='backstage row'>
+ <div className='backstage-content row'>
<div className='installed-integrations'>
- <div className='backstage__header'>
- <h1 className='text'>
+ <div className='backstage-header'>
+ <h1>
<FormattedMessage
id='installed_integrations.header'
defaultMessage='Installed Integrations'
@@ -269,17 +269,21 @@ export default class InstalledIntegrations extends React.Component {
</button>
</Link>
</div>
- <div className='installed-integrations__filters'>
+ <div className='backstage-filters'>
{this.renderTypeFilters(this.state.incomingWebhooks, this.state.outgoingWebhooks)}
- <input
- type='search'
- placeholder={Utils.localizeMessage('installed_integrations.search', 'Search Integrations')}
- value={this.state.filter}
- onChange={this.updateFilter}
- style={{flexGrow: 0, flexShrink: 0}}
- />
+ <div className='backstage-filter__search'>
+ <i className='fa fa-search'></i>
+ <input
+ type='search'
+ className='form-control'
+ placeholder={Utils.localizeMessage('installed_integrations.search', 'Search Integrations')}
+ value={this.state.filter}
+ onChange={this.updateFilter}
+ style={{flexGrow: 0, flexShrink: 0}}
+ />
+ </div>
</div>
- <div className='installed-integrations__list'>
+ <div className='backstage-list'>
{integrations}
</div>
</div>
diff --git a/webapp/components/backstage/installed_outgoing_webhook.jsx b/webapp/components/backstage/installed_outgoing_webhook.jsx
index 12e1a5c81..fee427260 100644
--- a/webapp/components/backstage/installed_outgoing_webhook.jsx
+++ b/webapp/components/backstage/installed_outgoing_webhook.jsx
@@ -43,21 +43,21 @@ export default class InstalledOutgoingWebhook extends React.Component {
const channelName = channel ? channel.display_name : 'cannot find channel';
return (
- <div className='installed-integrations__item installed-integrations__outgoing-webhook'>
- <div className='details'>
- <div className='details-row'>
- <span className='name'>
+ <div className='backstage-list__item'>
+ <div className='item-details'>
+ <div className='item-details__row'>
+ <span className='item-details__name'>
{channelName}
</span>
- <span className='type'>
+ <span className='item-details__type'>
<FormattedMessage
id='installed_integrations.outgoingWebhookType'
defaultMessage='(Outgoing Webhook)'
/>
</span>
</div>
- <div className='details-row'>
- <span className='description'>
+ <div className='item-details__row'>
+ <span className='item-details__description'>
{Utils.getWindowLocationOrigin() + '/hooks/' + outgoingWebhook.id}
{' - '}
{outgoingWebhook.token}
diff --git a/webapp/components/error_page.jsx b/webapp/components/error_page.jsx
new file mode 100644
index 000000000..53f0fce82
--- /dev/null
+++ b/webapp/components/error_page.jsx
@@ -0,0 +1,58 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import $ from 'jquery';
+
+import React from 'react';
+import {Link} from 'react-router';
+
+import * as Utils from 'utils/utils.jsx';
+
+export default class ErrorPage extends React.Component {
+ componentDidMount() {
+ $('body').attr('class', 'sticky error');
+ }
+ componentWillUnmount() {
+ $('body').attr('class', '');
+ }
+ render() {
+ let title = this.props.location.query.title;
+ if (!title || title === '') {
+ title = Utils.localizeMessage('error.generic.title', 'Error');
+ }
+
+ let message = this.props.location.query.message;
+ if (!message || message === '') {
+ message = Utils.localizeMessage('error.generic.message', 'An error has occoured.');
+ }
+
+ let link = this.props.location.query.link;
+ if (!link || link === '') {
+ link = '/';
+ }
+
+ let linkMessage = this.props.location.query.linkmessage;
+ if (!linkMessage || linkMessage === '') {
+ linkMessage = Utils.localizeMessage('error.generic.link_message', 'Back to Mattermost');
+ }
+
+ return (
+ <div className='container-fluid'>
+ <div className='error__container'>
+ <div className='error__icon'>
+ <i className='fa fa-exclamation-triangle'/>
+ </div>
+ <h2>{title}</h2>
+ <p>{message}</p>
+ <Link to={link}>{linkMessage}</Link>
+ </div>
+ </div>
+ );
+ }
+}
+
+ErrorPage.defaultProps = {
+};
+ErrorPage.propTypes = {
+ location: React.PropTypes.object
+};
diff --git a/webapp/components/post.jsx b/webapp/components/post.jsx
index 30c47ee22..7294cf163 100644
--- a/webapp/components/post.jsx
+++ b/webapp/components/post.jsx
@@ -129,6 +129,7 @@ export default class Post extends React.Component {
const post = this.props.post;
const parentPost = this.props.parentPost;
const posts = this.props.posts;
+ const mattermostLogo = Constants.MATTERMOST_ICON_SVG;
if (!post.props) {
post.props = {};
@@ -184,24 +185,22 @@ export default class Post extends React.Component {
let profilePic = null;
if (!this.props.hideProfilePic) {
- let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp;
- if (post.props && post.props.from_webhook && global.window.mm_config.EnablePostIconOverride === 'true') {
- if (post.props.override_icon_url) {
- src = post.props.override_icon_url;
- } else {
- src = Constants.DEFAULT_WEBHOOK_LOGO;
- }
- } else if (Utils.isSystemMessage(post)) {
- src = Constants.SYSTEM_MESSAGE_PROFILE_IMAGE;
- }
-
profilePic = (
<img
- src={src}
+ src={Utils.getProfilePicSrcForPost(post, timestamp)}
height='36'
width='36'
/>
);
+
+ if (Utils.isSystemMessage(post)) {
+ profilePic = (
+ <span
+ className='icon'
+ dangerouslySetInnerHTML={{__html: mattermostLogo}}
+ />
+ );
+ }
}
return (
diff --git a/webapp/components/post_body_additional_content.jsx b/webapp/components/post_body_additional_content.jsx
index 2cd82f213..452597dde 100644
--- a/webapp/components/post_body_additional_content.jsx
+++ b/webapp/components/post_body_additional_content.jsx
@@ -70,7 +70,7 @@ export default class PostBodyAdditionalContent extends React.Component {
return this.getSlackAttachment();
}
- const link = Utils.extractLinks(this.props.post.message)[0];
+ const link = Utils.extractFirstLink(this.props.post.message);
if (!link) {
return null;
}
diff --git a/webapp/components/posts_view.jsx b/webapp/components/posts_view.jsx
index 917411549..aa7f445ce 100644
--- a/webapp/components/posts_view.jsx
+++ b/webapp/components/posts_view.jsx
@@ -204,10 +204,12 @@ export default class PostsView extends React.Component {
// the previous post was made by the same user as the current post,
// the previous post is not a comment,
// the current post is not a comment,
+ // the previous post is not from a webhook
// the current post is not from a webhook
if (prevPostUserId === postUserId &&
!prevPostIsComment &&
!postIsComment &&
+ !prevPostFromWebhook &&
!postFromWebhook) {
hideProfilePic = true;
}
@@ -308,7 +310,7 @@ export default class PostsView extends React.Component {
if (this.props.scrollType === PostsView.SCROLL_TYPE_BOTTOM) {
this.scrollToBottom();
} else if (this.props.scrollType === PostsView.SCROLL_TYPE_NEW_MESSAGE) {
- window.requestAnimationFrame(() => {
+ window.setTimeout(window.requestAnimationFrame(() => {
// If separator exists scroll to it. Otherwise scroll to bottom.
if (this.refs.newMessageSeparator) {
var objDiv = this.refs.postlist;
@@ -316,7 +318,7 @@ export default class PostsView extends React.Component {
} else if (this.refs.postlist) {
this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight;
}
- });
+ }), 0);
} else if (this.props.scrollType === PostsView.SCROLL_TYPE_POST && this.props.scrollPostId) {
window.requestAnimationFrame(() => {
const postNode = ReactDOM.findDOMNode(this.refs[this.props.scrollPostId]);
diff --git a/webapp/components/rhs_root_post.jsx b/webapp/components/rhs_root_post.jsx
index 7a7c5f692..9a207e429 100644
--- a/webapp/components/rhs_root_post.jsx
+++ b/webapp/components/rhs_root_post.jsx
@@ -213,21 +213,10 @@ export default class RhsRootPost extends React.Component {
);
}
- let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp;
- if (post.props && post.props.from_webhook && global.window.mm_config.EnablePostIconOverride === 'true') {
- if (post.props.override_icon_url) {
- src = post.props.override_icon_url;
- } else {
- src = Constants.DEFAULT_WEBHOOK_LOGO;
- }
- } else if (Utils.isSystemMessage(post)) {
- src = Constants.SYSTEM_MESSAGE_PROFILE_IMAGE;
- }
-
const profilePic = (
<img
className='post-profile-img'
- src={src}
+ src={Utils.getProfilePicSrcForPost(post, timestamp)}
height='36'
width='36'
/>
diff --git a/webapp/components/search_results_item.jsx b/webapp/components/search_results_item.jsx
index 75cbcb2a0..58f09c0e0 100644
--- a/webapp/components/search_results_item.jsx
+++ b/webapp/components/search_results_item.jsx
@@ -1,11 +1,13 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import UserStore from 'stores/user_store.jsx';
import UserProfile from './user_profile.jsx';
+
+import UserStore from 'stores/user_store.jsx';
+
import * as GlobalActions from 'action_creators/global_actions.jsx';
import * as TextFormatting from 'utils/text_formatting.jsx';
-
+import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
import {FormattedMessage, FormattedDate} from 'react-intl';
@@ -29,6 +31,7 @@ export default class SearchResultsItem extends React.Component {
const channel = this.props.channel;
const timestamp = UserStore.getCurrentUser().update_at;
const user = this.props.user || {};
+ const post = this.props.post;
if (channel) {
channelName = channel.display_name;
@@ -47,13 +50,23 @@ export default class SearchResultsItem extends React.Component {
mentionHighlight: this.props.isMentionSearch
};
+ let overrideUsername;
+ let disableProfilePopover = false;
+ if (post.props &&
+ post.props.from_webhook &&
+ post.props.override_username &&
+ global.window.mm_config.EnablePostUsernameOverride === 'true') {
+ overrideUsername = post.props.override_username;
+ disableProfilePopover = true;
+ }
+
return (
<div className='search-item__container'>
<div className='date-separator'>
<hr className='separator__hr'/>
<div className='separator__text'>
<FormattedDate
- value={this.props.post.create_at}
+ value={post.create_at}
day='numeric'
month='long'
year='numeric'
@@ -67,18 +80,24 @@ export default class SearchResultsItem extends React.Component {
<div className='post__content'>
<div className='post__img'>
<img
- src={'/api/v1/users/' + this.props.post.user_id + '/image?time=' + timestamp}
+ src={Utils.getProfilePicSrcForPost(post, timestamp)}
height='36'
width='36'
/>
</div>
<div>
<ul className='post__header'>
- <li className='col__name'><strong><UserProfile user={user}/></strong></li>
+ <li className='col__name'><strong>
+ <UserProfile
+ user={user}
+ overwriteName={overrideUsername}
+ disablePopover={disableProfilePopover}
+ />
+ </strong></li>
<li className='col'>
<time className='search-item-time'>
<FormattedDate
- value={this.props.post.create_at}
+ value={post.create_at}
hour12={true}
hour='2-digit'
minute='2-digit'
@@ -87,7 +106,7 @@ export default class SearchResultsItem extends React.Component {
</li>
<li>
<Link
- to={'/' + window.location.pathname.split('/')[1] + '/pl/' + this.props.post.id}
+ to={'/' + window.location.pathname.split('/')[1] + '/pl/' + post.id}
className='search-item__jump'
>
<FormattedMessage
@@ -112,7 +131,7 @@ export default class SearchResultsItem extends React.Component {
<div className='search-item-snippet'>
<span
onClick={TextFormatting.handleClick}
- dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.props.post.message, formattingOptions)}}
+ dangerouslySetInnerHTML={{__html: TextFormatting.formatText(post.message, formattingOptions)}}
/>
</div>
</div>
diff --git a/webapp/components/sidebar.jsx b/webapp/components/sidebar.jsx
index b22d3ec34..500e73cf2 100644
--- a/webapp/components/sidebar.jsx
+++ b/webapp/components/sidebar.jsx
@@ -138,7 +138,9 @@ export default class Sidebar extends React.Component {
unreadCounts: JSON.parse(JSON.stringify(ChannelStore.getUnreadCounts())),
showTutorialTip: tutorialStep === TutorialSteps.CHANNEL_POPOVER,
currentTeam: TeamStore.getCurrent(),
- currentUser: UserStore.getCurrentUser()
+ currentUser: UserStore.getCurrentUser(),
+ townSquare: ChannelStore.getByName(Constants.DEFAULT_CHANNEL),
+ offTopic: ChannelStore.getByName(Constants.OFFTOPIC_CHANNEL)
};
}
@@ -290,6 +292,16 @@ export default class Sidebar extends React.Component {
createTutorialTip() {
const screens = [];
+ let townSquareDisplayName = Constants.DEFAULT_CHANNEL_UI_NAME;
+ if (this.state.townSquare) {
+ townSquareDisplayName = this.state.townSquare.display_name;
+ }
+
+ let offTopicDisplayName = Constants.OFFTOPIC_CHANNEL_UI_NAME;
+ if (this.state.offTopic) {
+ offTopicDisplayName = this.state.offTopic.display_name;
+ }
+
screens.push(
<div>
<FormattedHTMLMessage
@@ -303,10 +315,14 @@ export default class Sidebar extends React.Component {
<div>
<FormattedHTMLMessage
id='sidebar.tutorialScreen2'
- defaultMessage='<h4>"Town Square" and "Off-Topic" channels</h4>
+ defaultMessage='<h4>"{townsquare}" and "{offtopic}" channels</h4>
<p>Here are two public channels to start:</p>
- <p><strong>Town Square</strong> is a place for team-wide communication. Everyone in your team is a member of this channel.</p>
- <p><strong>Off-Topic</strong> is a place for fun and humor outside of work-related channels. You and your team can decide what other channels to create.</p>'
+ <p><strong>{townsquare}</strong> is a place for team-wide communication. Everyone in your team is a member of this channel.</p>
+ <p><strong>{offtopic}</strong> is a place for fun and humor outside of work-related channels. You and your team can decide what other channels to create.</p>'
+ values={{
+ townsquare: townSquareDisplayName,
+ offtopic: offTopicDisplayName
+ }}
/>
</div>
);
diff --git a/webapp/components/suggestion/at_mention_provider.jsx b/webapp/components/suggestion/at_mention_provider.jsx
index b423528c3..90ec6e660 100644
--- a/webapp/components/suggestion/at_mention_provider.jsx
+++ b/webapp/components/suggestion/at_mention_provider.jsx
@@ -100,13 +100,16 @@ export default class AtMentionProvider {
}
}
- // add dummy users to represent the @all and @channel special mentions
- if ('all'.startsWith(usernamePrefix)) {
- filtered.push({username: 'all'});
- }
+ //Don't imply that @all and @channel can be direct messaged
+ if (!pretext.startsWith('/msg')) {
+ // add dummy users to represent the @all and @channel special mentions
+ if ('all'.startsWith(usernamePrefix)) {
+ filtered.push({username: 'all'});
+ }
- if ('channel'.startsWith(usernamePrefix)) {
- filtered.push({username: 'channel'});
+ if ('channel'.startsWith(usernamePrefix)) {
+ filtered.push({username: 'channel'});
+ }
}
filtered = filtered.sort((a, b) => a.username.localeCompare(b.username));
diff --git a/webapp/components/tutorial/tutorial_intro_screens.jsx b/webapp/components/tutorial/tutorial_intro_screens.jsx
index bad426cfc..0358a6a65 100644
--- a/webapp/components/tutorial/tutorial_intro_screens.jsx
+++ b/webapp/components/tutorial/tutorial_intro_screens.jsx
@@ -19,6 +19,12 @@ const NUM_SCREENS = 3;
import React from 'react';
export default class TutorialIntroScreens extends React.Component {
+ static get propTypes() {
+ return {
+ townSquare: React.PropTypes.object,
+ offTopic: React.PropTypes.object
+ };
+ }
constructor(props) {
super(props);
@@ -153,6 +159,11 @@ export default class TutorialIntroScreens extends React.Component {
);
}
+ let townSquareDisplayName = Constants.DEFAULT_CHANNEL_UI_NAME;
+ if (this.props.townSquare) {
+ townSquareDisplayName = this.props.townSquare.display_name;
+ }
+
return (
<div>
<h3>
@@ -171,7 +182,10 @@ export default class TutorialIntroScreens extends React.Component {
{supportInfo}
<FormattedMessage
id='tutorial_intro.end'
- defaultMessage='Click “Next” to enter Town Square. This is the first channel teammates see when they sign up. Use it for posting updates everyone needs to know.'
+ defaultMessage='Click “Next” to enter {channel}. This is the first channel teammates see when they sign up. Use it for posting updates everyone needs to know.'
+ values={{
+ channel: townSquareDisplayName
+ }}
/>
{circles}
</div>
diff --git a/webapp/components/tutorial/tutorial_view.jsx b/webapp/components/tutorial/tutorial_view.jsx
index d9e0ef40d..5f2c1a257 100644
--- a/webapp/components/tutorial/tutorial_view.jsx
+++ b/webapp/components/tutorial/tutorial_view.jsx
@@ -1,18 +1,43 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import React from 'react';
-
import TutorialIntroScreens from './tutorial_intro_screens.jsx';
+import ChannelStore from 'stores/channel_store.jsx';
+import Constants from 'utils/constants.jsx';
+
+import React from 'react';
+
export default class TutorialView extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleChannelChange = this.handleChannelChange.bind(this);
+
+ this.state = {
+ townSquare: ChannelStore.getByName(Constants.DEFAULT_CHANNEL)
+ };
+ }
+ componentDidMount() {
+ ChannelStore.addChangeListener(this.handleChannelChange);
+ }
+ componentWillUnmount() {
+ ChannelStore.removeChangeListener(this.handleChannelChange);
+ }
+ handleChannelChange() {
+ this.setState({
+ townSquare: ChannelStore.getByName(Constants.DEFAULT_CHANNEL)
+ });
+ }
render() {
return (
<div
id='app-content'
className='app__content'
>
- <TutorialIntroScreens/>
+ <TutorialIntroScreens
+ townSquare={this.state.townSquare}
+ />
</div>
);
}
diff --git a/webapp/components/user_settings/user_settings_display.jsx b/webapp/components/user_settings/user_settings_display.jsx
index d815bd371..d169e01b5 100644
--- a/webapp/components/user_settings/user_settings_display.jsx
+++ b/webapp/components/user_settings/user_settings_display.jsx
@@ -304,7 +304,7 @@ export default class UserSettingsDisplay extends React.Component {
describe = (
<FormattedMessage
id='user.settings.display.showUsername'
- defaultMessage='Show username (team default)'
+ defaultMessage='Show username (default)'
/>
);
} else if (this.state.nameFormat === 'full_name') {
diff --git a/webapp/components/user_settings/user_settings_security.jsx b/webapp/components/user_settings/user_settings_security.jsx
index e4044e6d0..ff5a898a9 100644
--- a/webapp/components/user_settings/user_settings_security.jsx
+++ b/webapp/components/user_settings/user_settings_security.jsx
@@ -1,6 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import $ from 'jquery';
import SettingItemMin from '../setting_item_min.jsx';
import SettingItemMax from '../setting_item_max.jsx';
import AccessHistoryModal from '../access_history_modal.jsx';
@@ -247,7 +248,7 @@ class SecurityTab extends React.Component {
extraInfo = (
<span>
<FormattedMessage
- id='user.settings.mfa.addHelp'
+ id='user.settings.mfa.addHelpQr'
defaultMessage='Please scan the QR code with the Google Authenticator app on your smartphone and fill in the token with one provided by the app.'
/>
</span>
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index 6f23552b3..7dc6486ab 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -3,12 +3,17 @@
"about.date": "Build Date:",
"about.enterpriseEditione1": "Enterprise Edition",
"about.hash": "Build Hash:",
+ "about.copyright": "Copyright 2016 Mattermost, Inc. All rights reserved",
"about.licensed": "Licensed by:",
"about.number": "Build Number:",
"about.teamEditiont0": "Team Edition",
"about.teamEditiont1": "Enterprise Edition",
"about.title": "About Mattermost",
"about.version": "Version:",
+ "about.teamEditionSt": "All your team communication in one place, instantly searchable and accessible anywhere.",
+ "about.teamEditionLearn": "Join the Mattermost community at ",
+ "about.enterpriseEditionSt": "Modern enterprise communication from behind your firewall.",
+ "about.enterpriseEditionLearn": "Learn more about Enterprise Edition at ",
"access_history.title": "Access History",
"activity_log.activeSessions": "Active Sessions",
"activity_log.browser": "Browser: {browser}",
@@ -30,10 +35,10 @@
"add_incoming_webhook.name": "Name",
"add_incoming_webhook.save": "Save",
"add_integration.header": "Add Integration",
- "add_integration.incomingWebhook.title": "Incoming Webhook",
"add_integration.incomingWebhook.description": "Create webhook URLs for use in external integrations.",
- "add_integration.outgoingWebhook.title": "Outgoing Webhook",
+ "add_integration.incomingWebhook.title": "Incoming Webhook",
"add_integration.outgoingWebhook.description": "Create webhooks to send new message events to an external integration.",
+ "add_integration.outgoingWebhook.title": "Outgoing Webhook",
"add_outgoing_webhook.callbackUrls": "Callback URLs (One Per Line)",
"add_outgoing_webhook.callbackUrlsRequired": "One or more callback URLs are required",
"add_outgoing_webhook.cancel": "Cancel",
@@ -43,17 +48,19 @@
"add_outgoing_webhook.name": "Name",
"add_outgoing_webhook.save": "Save",
"add_outgoing_webhook.triggerWOrds": "Trigger Words (One Per Line)",
+ "add_outgoing_webhook.triggerWords": "Trigger Words (One Per Line)",
"add_outgoing_webhook.triggerWordsOrChannelRequired": "A valid channel or a list of trigger words is required",
"admin.audits.reload": "Reload",
"admin.audits.title": "User Activity",
"admin.compliance.directoryDescription": "Directory to which compliance reports are written. If blank, will be set to ./data/.",
"admin.compliance.directoryExample": "Ex \"./data/\"",
"admin.compliance.directoryTitle": "Compliance Directory Location:",
+ "admin.compliance.enableDailyDesc": "When true, Mattermost will generate a daily compliance report.",
"admin.compliance.enableDailyTitle": "Enable Daily Report:",
- "admin.compliance.enableDesc": "When true, Mattermost will generate a daily compliance report.",
+ "admin.compliance.enableDesc": "When true, Mattermost allows compliance reporting",
"admin.compliance.enableTitle": "Enable Compliance:",
"admin.compliance.false": "false",
- "admin.compliance.noLicense": "<h4 class=\"banner__heading\">Note:</h4><p>Compliance is an enterprise feature. Your current license does not support Compliance. Click <a href=\"http://mattermost.com\" target=\"_blank\">here</a> for information and pricing on enterprise licenses.</p>",
+ "admin.compliance.noLicense": "<h4 class=\"banner__heading\">Note:</h4><p>Compliance is an enterprise feature. Your current license does not support Compliance. Click <a href=\"http://mattermost.com\"target=\"_blank\">here</a> for information and pricing on enterprise licenses.</p>",
"admin.compliance.save": "Save",
"admin.compliance.saving": "Saving Config...",
"admin.compliance.title": "Compliance Settings",
@@ -233,7 +240,7 @@
"admin.ldap.lastnameAttrDesc": "The attribute in the LDAP server that will be used to populate the last name of users in Mattermost.",
"admin.ldap.lastnameAttrEx": "Ex \"sn\"",
"admin.ldap.lastnameAttrTitle": "Last Name Attribute:",
- "admin.ldap.noLicense": "<h4 class=\"banner__heading\">Note:</h4><p>LDAP is an enterprise feature. Your current license does not support LDAP. Click <a href=\"http://mattermost.com\" target=\"_blank\">here</a> for information and pricing on enterprise licenses.</p>",
+ "admin.ldap.noLicense": "<h4 class=\"banner__heading\">Note:</h4><p>LDAP is an enterprise feature. Your current license does not support LDAP. Click <a href=\"http://mattermost.com\"target=\"_blank\">here</a> for information and pricing on enterprise licenses.</p>",
"admin.ldap.portDesc": "The port Mattermost will use to connect to the LDAP server. Default is 389.",
"admin.ldap.portEx": "Ex \"389\"",
"admin.ldap.portTitle": "LDAP Port:",
@@ -251,7 +258,10 @@
"admin.ldap.usernameAttrEx": "Ex \"sAMAccountName\"",
"admin.ldap.usernameAttrTitle": "Username Attribute:",
"admin.licence.keyMigration": "If you’re migrating servers you may need to remove your license key from this server in order to install it on a new server. To start, <a href=\"http://mattermost.com\" target=\"_blank\">disable all Enterprise Edition features on this server</a>. This will enable the ability to remove the license key and downgrade this server from Enterprise Edition to Team Edition.",
+ "admin.license.choose": "Choose File",
"admin.license.chooseFile": "Choose File",
+ "admin.license.edition": "Edition: ",
+ "admin.license.key": "License Key: ",
"admin.license.keyRemove": "Remove Enterprise License and Downgrade Server",
"admin.license.noFile": "No file uploaded",
"admin.license.removing": "Removing License...",
@@ -328,15 +338,13 @@
"admin.select_team.close": "Close",
"admin.select_team.select": "Select",
"admin.select_team.selectTeam": "Select Team",
- "admin.service.mfaTitle": "Enable Multi-factor Authentication:",
- "admin.service.mfaDesc": "When true, users will be given the option to add multi-factor authentication to their account. They will need a smartphone and an authenticator app such as Google Authenticator.",
"admin.service.attemptDescription": "Login attempts allowed before user is locked out and required to reset password via email.",
"admin.service.attemptExample": "Ex \"10\"",
"admin.service.attemptTitle": "Maximum Login Attempts:",
"admin.service.cmdsDesc": "When true, user created slash commands will be allowed.",
"admin.service.cmdsTitle": "Enable Slash Commands: ",
- "admin.service.corsDescription": "Enable HTTP Cross origin request from specific domains (separate by a spacebar). Use \"*\" if you want to allow CORS from any domain or leave it blank to disable it.",
- "admin.service.corsEx": "http://example.com https://example.com",
+ "admin.service.corsDescription": "Enable HTTP Cross origin request from a specific domain. Use \"*\" if you want to allow CORS from any domain or leave it blank to disable it.",
+ "admin.service.corsEx": "http://example.com",
"admin.service.corsTitle": "Allow Cross-origin Requests from:",
"admin.service.developerDesc": "(Developer Option) When true, extra information around errors will be displayed in the UI.",
"admin.service.developerTitle": "Enable Developer Mode: ",
@@ -353,6 +361,8 @@
"admin.service.listenAddress": "Listen Address:",
"admin.service.listenDescription": "The address to which to bind and listen. Entering \":8065\" will bind to all interfaces or you can choose one like \"127.0.0.1:8065\". Changing this will require a server restart before taking effect.",
"admin.service.listenExample": "Ex \":8065\"",
+ "admin.service.mfaDesc": "When true, users will be given the option to add multi-factor authentication to their account. They will need a smartphone and an authenticator app such as Google Authenticator.",
+ "admin.service.mfaTitle": "Enable Multi-factor Authentication:",
"admin.service.mobileSessionDays": "Session Length for Mobile Device in Days:",
"admin.service.mobileSessionDaysDesc": "The native mobile session will expire after the number of days specified and will require a user to login again.",
"admin.service.outWebhooksDesc": "When true, outgoing webhooks will be allowed.",
@@ -512,6 +522,7 @@
"analytics.team.privateGroups": "Private Groups",
"analytics.team.publicChannels": "Public Channels",
"analytics.team.recentActive": "Recent Active Users",
+ "analytics.team.recentUsers": "Recent Active Users",
"analytics.team.title": "Team Statistics for {team}",
"analytics.team.totalPosts": "Total Posts",
"analytics.team.totalUsers": "Total Users",
@@ -576,10 +587,10 @@
"authorize.title": "An application would like to connect to your {teamName} account",
"backstage_navbar.backToMattermost": "Back to {siteName}",
"backstage_sidebar.integrations": "Integrations",
- "backstage_sidebar.integrations.installed": "Installed Integrations",
"backstage_sidebar.integrations.add": "Add Integration",
"backstage_sidebar.integrations.add.incomingWebhook": "Incoming Webhook",
"backstage_sidebar.integrations.add.outgoingWebhook": "Outgoing Webhook",
+ "backstage_sidebar.integrations.installed": "Installed Integrations",
"center_panel.recent": "Click here to jump to recent messages. ",
"chanel_header.addMembers": "Add Members",
"change_url.close": "Close",
@@ -702,6 +713,7 @@
"claim.oauth_to_email.pwdNotMatch": "Password do not match.",
"claim.oauth_to_email.switchTo": "Switch {type} to email and password",
"claim.oauth_to_email.title": "Switch {type} Account to Email",
+ "claim.oauth_to_email_newPwd": "Enter a new password for your {team} {site} account",
"confirm_modal.cancel": "Cancel",
"create_comment.addComment": "Add a comment...",
"create_comment.comment": "Add Comment",
@@ -763,8 +775,9 @@
"file_upload.filesAbove": "Files above {max}MB could not be uploaded: {filenames}",
"file_upload.limited": "Uploads limited to {count} files maximum. Please use additional posts for more files.",
"file_upload.pasted": "Image Pasted at ",
- "filtered_user_list.count": "{count, number} {count, plural, one {member} other {members}}",
- "filtered_user_list.countTotal": "{count, number} {count, plural, one {member} other {members}} of {total} Total",
+ "filtered_user_list.count": "{count} {count, plural, one {member} other {members}}",
+ "filtered_user_list.countTotal": "{count} {count, plural, one {member} other {members}} of {total} Total",
+ "filtered_user_list.member": "Member",
"filtered_user_list.search": "Search members",
"find_team.email": "Email",
"find_team.findDescription": "An email was sent with links to any teams to which you are a member.",
@@ -800,13 +813,13 @@
"get_team_invite_link_modal.helpDisabled": "User creation has been disabled for your team. Please ask your team administrator for details.",
"get_team_invite_link_modal.title": "Team Invite Link",
"installed_integrations.add": "Add Integration",
- "installed_integrations.allFilter": "All",
+ "installed_integrations.allFilter": "All ({count})",
"installed_integrations.delete": "Delete",
"installed_integrations.header": "Installed Integrations",
- "installed_integrations.incomingWebhooksFilter": "Incoming Webhooks ({count})",
"installed_integrations.incomingWebhookType": "(Incoming Webhook)",
- "installed_integrations.outgoingWebhooksFilter": "Outgoing Webhooks ({count})",
+ "installed_integrations.incomingWebhooksFilter": "Incoming Webhooks ({count})",
"installed_integrations.outgoingWebhookType": "(Outgoing Webhook)",
+ "installed_integrations.outgoingWebhooksFilter": "Outgoing Webhooks ({count})",
"installed_integrations.regenToken": "Regen Token",
"installed_integrations.search": "Search Integrations",
"intro_messages.DM": "This is the start of your direct message history with {teammate}.<br />Direct messages and files shared here are not shown to people outside this area.",
@@ -859,10 +872,6 @@
"login.session_expired": " Your session has expired. Please login again.",
"login.signTo": "Sign in to:",
"login.verified": " Email Verified",
- "login_mfa.token": "MFA Token",
- "login_mfa.enterToken": "To complete the sign in process, please enter a token from your smartphone's authenticator",
- "login_mfa.submit": "Submit",
- "login_mfa.tokenReq": "Please enter an MFA token",
"login_email.badTeam": "Bad team name",
"login_email.email": "Email",
"login_email.emailReq": "An email is required",
@@ -875,6 +884,10 @@
"login_ldap.pwdReq": "An LDAP password is required",
"login_ldap.signin": "Sign in",
"login_ldap.username": "LDAP Username",
+ "login_mfa.enterToken": "To complete the sign in process, please enter a token from your smartphone's authenticator",
+ "login_mfa.submit": "Submit",
+ "login_mfa.token": "MFA Token",
+ "login_mfa.tokenReq": "Please enter an MFA token",
"login_username.badTeam": "Bad team name",
"login_username.pwd": "Password",
"login_username.pwdReq": "A password is required",
@@ -934,7 +947,7 @@
"password_form.title": "Password Reset",
"password_form.update": "Your password has been updated successfully.",
"password_send.checkInbox": "Please check your inbox.",
- "password_send.description": "To reset your password, enter the email address you used to sign up.",
+ "password_send.description": "To reset your password, enter the email address you used to sign up",
"password_send.email": "Email",
"password_send.error": "Please enter a valid email address.",
"password_send.link": "<p>A password reset link has been sent to <b>{email}</b></p>",
@@ -1031,7 +1044,7 @@
"sidebar.pg": "Private Groups",
"sidebar.removeList": "Remove from list",
"sidebar.tutorialScreen1": "<h4>Channels</h4><p><strong>Channels</strong> organize conversations across different topics. They’re open to everyone on your team. To send private communications use <strong>Direct Messages</strong> for a single person or <strong>Private Groups</strong> for multiple people.</p>",
- "sidebar.tutorialScreen2": "<h4>\"Town Square\" and \"Off-Topic\" channels</h4><p>Here are two public channels to start:</p><p><strong>Town Square</strong> is a place for team-wide communication. Everyone in your team is a member of this channel.</p><p><strong>Off-Topic</strong> is a place for fun and humor outside of work-related channels. You and your team can decide what other channels to create.</p>",
+ "sidebar.tutorialScreen2": "<h4>\"{townsquare}\" and \"{offtopic}\" channels</h4><p>Here are two public channels to start:</p><p><strong>{townsquare}</strong> is a place for team-wide communication. Everyone in your team is a member of this channel.</p><p><strong>{offtopic}</strong> is a place for fun and humor outside of work-related channels. You and your team can decide what other channels to create.</p>",
"sidebar.tutorialScreen3": "<h4>Creating and Joining Channels</h4><p>Click <strong>\"More...\"</strong> to create a new channel or join an existing one.</p><p>You can also create a new channel or private group by clicking the <strong>\"+\" symbol</strong> next to the channel or private group header.</p>",
"sidebar.unreadAbove": "Unread post(s) above",
"sidebar.unreadBelow": "Unread post(s) below",
@@ -1073,6 +1086,7 @@
"signup_user_completed.validEmail": "Please enter a valid email address",
"signup_user_completed.welcome": "Welcome to:",
"signup_user_completed.whatis": "What's your email address?",
+ "signup_user_completed.withLdap": "With your LDAP credentials",
"sso_signup.find": "Find my teams",
"sso_signup.gitlab": "Create team with GitLab Account",
"sso_signup.google": "Create team with Google Apps Account",
@@ -1179,7 +1193,7 @@
"textbox.quote": ">quote",
"textbox.strike": "strike",
"tutorial_intro.allSet": "You’re all set",
- "tutorial_intro.end": "Click “Next” to enter Town Square. This is the first channel teammates see when they sign up. Use it for posting updates everyone needs to know.",
+ "tutorial_intro.end": "Click “Next” to enter {channel}. This is the first channel teammates see when they sign up. Use it for posting updates everyone needs to know.",
"tutorial_intro.invite": "Invite teammates",
"tutorial_intro.next": "Next",
"tutorial_intro.screenOne": "<h3>Welcome to:</h3><h1>Mattermost</h1><p>Your team communication all in one place, instantly searchable and available anywhere</p><p>Keep your team connected to help them achieve what matters most.</p>",
@@ -1294,11 +1308,11 @@
"user.settings.general.confirmEmail": "Confirm Email",
"user.settings.general.email": "Email",
"user.settings.general.emailGitlabCantUpdate": "Login occurs through GitLab. Email cannot be updated. Email address used for notifications is {email}.",
- "user.settings.general.emailLdapCantUpdate": "Login occurs through LDAP. Email cannot be updated. Email address used for notifications is {email}.",
"user.settings.general.emailHelp1": "Email is used for sign-in, notifications, and password reset. Email requires verification if changed.",
"user.settings.general.emailHelp2": "Email has been disabled by your system administrator. No notification emails will be sent until it is enabled.",
"user.settings.general.emailHelp3": "Email is used for sign-in, notifications, and password reset.",
"user.settings.general.emailHelp4": "A verification email was sent to {email}.",
+ "user.settings.general.emailLdapCantUpdate": "Login occurs through LDAP. Email cannot be updated. Email address used for notifications is {email}.",
"user.settings.general.emailMatch": "The new emails you entered do not match.",
"user.settings.general.emptyName": "Click 'Edit' to add your full name",
"user.settings.general.emptyNickname": "Click 'Edit' to add a nickname",
@@ -1333,6 +1347,13 @@
"user.settings.integrations.commandsDescription": "Manage your slash commands",
"user.settings.integrations.title": "Integration Settings",
"user.settings.languages.change": "Change interface language",
+ "user.settings.mfa.add": "Add MFA to your account",
+ "user.settings.mfa.addHelp": "To add multi-factor authentication to your account you must have a smartphone with Google Authenticator installed.",
+ "user.settings.mfa.addHelpQr": "Please scan the QR code with the Google Authenticator app on your smartphone and fill in the token with one provided by the app.",
+ "user.settings.mfa.enterToken": "Token",
+ "user.settings.mfa.qrCode": "QR Code",
+ "user.settings.mfa.remove": "Remove MFA from your account",
+ "user.settings.mfa.removeHelp": "Removing multi-factor authentication will make your account more vulnerable to attacks.",
"user.settings.modal.advanced": "Advanced",
"user.settings.modal.confirmBtns": "Yes, Discard",
"user.settings.modal.confirmMsg": "You have unsaved changes, are you sure you want to discard them?",
@@ -1388,7 +1409,7 @@
"user.settings.security.switchEmail": "Switch to using email and password",
"user.settings.security.switchGitlab": "Switch to using GitLab SSO",
"user.settings.security.switchGoogle": "Switch to using Google SSO",
- "user.settings.security.switchLda": "Switch to using LDAP",
+ "user.settings.security.switchLdap": "Switch to using LDAP",
"user.settings.security.title": "Security Settings",
"user.settings.security.viewHistory": "View Access History",
"user_list.notFound": "No users found :(",
diff --git a/webapp/i18n/es.json b/webapp/i18n/es.json
index d30792b8c..8cc9e5db6 100644
--- a/webapp/i18n/es.json
+++ b/webapp/i18n/es.json
@@ -22,13 +22,37 @@
"activity_log_modal.android": "Android",
"activity_log_modal.androidNativeApp": "Android App Nativa",
"activity_log_modal.iphoneNativeApp": "iPhone App Nativa",
+ "add_incoming_webhook.cancel": "Cancelar",
+ "add_incoming_webhook.channel": "Canal",
+ "add_incoming_webhook.channelRequired": "Es obligatorio asignar un canal válido",
+ "add_incoming_webhook.description": "Descripción",
+ "add_incoming_webhook.header": "Agregar un Webhook de Entrada",
+ "add_incoming_webhook.name": "Nombre",
+ "add_incoming_webhook.save": "Guardar",
+ "add_integration.header": "Agregar Integración",
+ "add_integration.incomingWebhook.description": "Crea webhook URLs para utilizarlas con integraciones externas.",
+ "add_integration.incomingWebhook.title": "Webhook de Entrada",
+ "add_integration.outgoingWebhook.description": "Crea webhooks para enviar mensajes a integraciones externas.",
+ "add_integration.outgoingWebhook.title": "Webhook de salida",
+ "add_outgoing_webhook.callbackUrls": "Callback URLs (Uno por Línea)",
+ "add_outgoing_webhook.callbackUrlsRequired": "Se require uno o más URLs para los callback",
+ "add_outgoing_webhook.cancel": "Cancelar",
+ "add_outgoing_webhook.channel": "Canal",
+ "add_outgoing_webhook.description": "Descripción",
+ "add_outgoing_webhook.header": "Agregar Webhook de Salida",
+ "add_outgoing_webhook.name": "Nombre",
+ "add_outgoing_webhook.save": "Guardar",
+ "add_outgoing_webhook.triggerWOrds": "Palabras gatilladoras (Una por línea)",
+ "add_outgoing_webhook.triggerWords": "Palabras gatilladoras (Una por Línea)",
+ "add_outgoing_webhook.triggerWordsOrChannelRequired": "Se require al menos un canal válido o una lista de palabras gatilladoras",
"admin.audits.reload": "Recargar",
"admin.audits.title": "Auditorías del Servidor",
"admin.compliance.directoryDescription": "Directorio en el que se escriben los informes de cumplimiento. Si se deja en blanco, se utilizará ./data/.",
"admin.compliance.directoryExample": "Ej \"./data/\"",
"admin.compliance.directoryTitle": "Ubicación del Directorio de Cumplimiento:",
+ "admin.compliance.enableDailyDesc": "Cuando es verdadero, Mattermost generará un reporte de cumplimiento diario.",
"admin.compliance.enableDailyTitle": "Habilitar Informes Diarios:",
- "admin.compliance.enableDesc": "Cuando es verdadero, Mattermost generará un informe diario de cumplimiento.",
+ "admin.compliance.enableDesc": "Cuando es verdadero, Mattermost permite la creación de reportes de cumplimiento",
"admin.compliance.enableTitle": "Habilitar el Cumplimiento:",
"admin.compliance.false": "falso",
"admin.compliance.noLicense": "<h4 class=\"banner__heading\">Nota:</h4><p>El Cumplimiento es una característica de la edición enterprise. Tu licencia actual no soporta Cumplimiento. Pincha <a href=\"http://mattermost.com\" target=\"_blank\">aquí</a> para información y precio de las licencias enterprise.</p>",
@@ -211,7 +235,7 @@
"admin.ldap.lastnameAttrDesc": "El atributo en el servidor LDAP que será utilizado para poblar el apellido de los usuarios en Mattermost.",
"admin.ldap.lastnameAttrEx": "Ej \"sn\"",
"admin.ldap.lastnameAttrTitle": "Atributo Apellido:",
- "admin.ldap.noLicense": "<h4 class=\"banner__heading\">Nota:</h4><p>LDAP es una característica de la edición enterprise. Tu licencia actual no soporta LDAP. Pincha <a href=\"http://mattermost.com\" target=\"_blank\">aquí</a> para obtener información y precios de las licencias de la edición enterprise.</p>",
+ "admin.ldap.noLicense": "<h4 class=\"banner__heading\">Nota:</h4><p>LDAP es una característica de la edición enterprise. Tu licencia actual no soporta LDAP. Pincha <a href=\"http://mattermost.com\" target=\"_blank\">aquí</a> para obtener información y precios de las licencias enterprise.</p>",
"admin.ldap.portDesc": "El puerto que Mattermost utilizará para conectarse al servidor LDAP. El predeterminado es 389.",
"admin.ldap.portEx": "Ej \"389\"",
"admin.ldap.portTitle": "Puerto LDAP:",
@@ -229,7 +253,10 @@
"admin.ldap.usernameAttrEx": "Ej \"sAMAccountName\"",
"admin.ldap.usernameAttrTitle": "Atributo Usuario:",
"admin.licence.keyMigration": "Si estás migrando servidores es posible que necesites remover tu licencia de este servidor para poder instalarlo en un servidor nuevo. Para empezar, <a href=\"http://mattermost.com\" target=\"_blank\">deshabilita todas las características de la Edición Enterprise de este servidor</a>. Esta operación habilitará la opción para remover la licencia y degradar este servidor de la Edición Enterprise a la Edición Team.",
+ "admin.license.choose": "Seleccionar Archivo",
"admin.license.chooseFile": "Escoger Archivo",
+ "admin.license.edition": "Edición: ",
+ "admin.license.key": "Licencia: ",
"admin.license.keyRemove": "Remover la Licencia Enterprise y Degradar el Servidor",
"admin.license.noFile": "No se subió ningún archivo",
"admin.license.removing": "Removiendo Licencia...",
@@ -311,8 +338,8 @@
"admin.service.attemptTitle": "Máximo de intentos de conexión:",
"admin.service.cmdsDesc": "Cuando es verdadero, se permite la creación de comandos de barra por usuarios.",
"admin.service.cmdsTitle": "Habilitar Comandos de Barra: ",
- "admin.service.corsDescription": "Habilita las solicitudes HTTP de origen cruzado para dominios en específico (separados por un espacio). Utiliza \"*\" si quieres habilitar CORS desde cualquier dominio o deja el campo en blanco para deshabilitarlo.",
- "admin.service.corsEx": "http://ejemplo.com https://ejemplo.com",
+ "admin.service.corsDescription": "Habilitar solicitudes HTTP de origen cruzado desde un dominio específico. Utiliza \"*\" si quieres permitir CORS desde cualquier dominio o dejalo en blanco para deshabilitarlo.",
+ "admin.service.corsEx": "http://ejemplo.com",
"admin.service.corsTitle": "Permitir Solicitudes de Origen Cruzado desde:",
"admin.service.developerDesc": "(Opción de Desarrollador) Cuando está asignado en verdadero, información extra sobre errores se muestra en el UI.",
"admin.service.developerTitle": "Habilitar modo de Desarrollador: ",
@@ -329,6 +356,8 @@
"admin.service.listenAddress": "Dirección de escucha:",
"admin.service.listenDescription": "La dirección a la que se unirá y escuchará. Ingresar \":8065\" se podrá unir a todas las interfaces o podrá seleccionar una como ej: \"127.0.0.1:8065\". Cambiando este valor es necesario reiniciar el servidor.",
"admin.service.listenExample": "Ej \":8065\"",
+ "admin.service.mfaDesc": "Cuando es verdadero, los usuarios tendrán la opción de agregar autenticación de múltiples factores a sus cuentas. Necesitarán un teléfono inteligente y una app de autenticación como Google Authenticator.",
+ "admin.service.mfaTitle": "Habilitar Autenticación de Múltiples Factores:",
"admin.service.mobileSessionDays": "Duración de la Sesión en Días para Dispositivos Moviles:",
"admin.service.mobileSessionDaysDesc": "La sesión nativa de los dispositivos moviles expirará luego de transcurrido el numero de días especificado y se solicitará al usuario que inicie sesión nuevamente.",
"admin.service.outWebhooksDesc": "Cuando es verdadero, los webhooks de salida serán permitidos.",
@@ -488,6 +517,7 @@
"analytics.team.privateGroups": "Grupos Privados",
"analytics.team.publicChannels": "Canales Públicos",
"analytics.team.recentActive": "Usuarios Recientemente Activos",
+ "analytics.team.recentUsers": "Usuarios Recientemente Activos",
"analytics.team.title": "Estádisticas del Equipo {team}",
"analytics.team.totalPosts": "Total de Mensajes",
"analytics.team.totalUsers": "Total de Usuarios",
@@ -550,6 +580,12 @@
"authorize.app": "La app <strong>{appName}</strong> quiere tener la habilidad de accesar y modificar tu información básica.",
"authorize.deny": "Denegar",
"authorize.title": "Una aplicación quiere conectarse con tu cuenta de {teamName}",
+ "backstage_navbar.backToMattermost": "Volver a {siteName}",
+ "backstage_sidebar.integrations": "Integraciones",
+ "backstage_sidebar.integrations.add": "Agregar Integración",
+ "backstage_sidebar.integrations.add.incomingWebhook": "Webhook de Entrada",
+ "backstage_sidebar.integrations.add.outgoingWebhook": "Webhook de Salida",
+ "backstage_sidebar.integrations.installed": "Integraciones Instaladas",
"center_panel.recent": "Pincha aquí para ir a los mensajes más recientes. ",
"chanel_header.addMembers": "Agregar Miembros",
"change_url.close": "Cerrar",
@@ -626,6 +662,7 @@
"channel_notifications.preferences": "Preferencias de Notificación para ",
"channel_notifications.sendDesktop": "Enviar notificaciones de escritorio",
"channel_notifications.unreadInfo": "El nombre del canal está en negritas en la barra lateral cuando hay mensajes sin leer. Al elegir \"Sólo para menciones\" sólo lo dejará en negritas cuando seas mencionado.",
+ "channel_select.placeholder": "--- Selecciona un canal ---",
"choose_auth_page.emailCreate": "Crea un nuevo equipo con tu cuenta de correo",
"choose_auth_page.find": "Encontrar mi equipo",
"choose_auth_page.gitlabCreate": "Crear un nuevo equipo con una cuenta de GitLab",
@@ -671,6 +708,7 @@
"claim.oauth_to_email.pwdNotMatch": "Las contraseñas no coinciden.",
"claim.oauth_to_email.switchTo": "Cambiar {type} a correo electrónico y contraseña",
"claim.oauth_to_email.title": "Cambiar la cuenta de {type} a Correo Electrónico",
+ "claim.oauth_to_email_newPwd": "Ingresa una nueva contraseña para tu cuenta de {team} en {site}",
"confirm_modal.cancel": "Cancelar",
"create_comment.addComment": "Agregar un comentario...",
"create_comment.comment": "Agregar Comentario",
@@ -732,8 +770,9 @@
"file_upload.filesAbove": "No se pueden subir archivos de más de {max}MB: {filenames}",
"file_upload.limited": "Se pueden subir un máximo de {count} archivos. Por favor envía otros mensajes para adjuntar más archivos.",
"file_upload.pasted": "Imagen Pegada el ",
- "filtered_user_list.count": "{count, number} {count, plural, one {Miembro} other {Miembros}}",
- "filtered_user_list.countTotal": "{count, number} {count, plural, one {Miembro} other {Miembros}} de {total} Total",
+ "filtered_user_list.count": "{count} {count, plural, one {miembro} other {miembros}}",
+ "filtered_user_list.countTotal": "{count} {count, plural, one {miembro} other {miembros}} de {total} Total",
+ "filtered_user_list.member": "Miembro",
"filtered_user_list.search": "Buscar miembros",
"find_team.email": "Correo electrónico",
"find_team.findDescription": "Enviamos un correo electrónico con los equipos a los que perteneces.",
@@ -768,6 +807,16 @@
"get_team_invite_link_modal.help": "Envía el siguiente enlace a tus compañeros para que se registren a este equipo. El enlace de invitación al equipo puede ser compartido con multiples compañeros y el mismo no cambiará a menos que sea regenerado en la Configuración del Equipo por un Administrador del Equipo.",
"get_team_invite_link_modal.helpDisabled": "La creación de usuario ha sido deshabilitada para tu equipo. Por favor solicita más detalles a tu administrador de equipo.",
"get_team_invite_link_modal.title": "Enlace de Invitación al Equipo",
+ "installed_integrations.add": "Agregar Integración",
+ "installed_integrations.allFilter": "Todos ({count})",
+ "installed_integrations.delete": "Eliminar",
+ "installed_integrations.header": "Integraciones Instaladas",
+ "installed_integrations.incomingWebhookType": "(Webhook de Entrada)",
+ "installed_integrations.incomingWebhooksFilter": "Webhooks de Entrada ({count})",
+ "installed_integrations.outgoingWebhookType": "(Webhook de Salida)",
+ "installed_integrations.outgoingWebhooksFilter": "Webhooks de Salida ({count})",
+ "installed_integrations.regenToken": "Regenerar Token",
+ "installed_integrations.search": "Buscar Integraciones",
"intro_messages.DM": "Este es el inicio de tu historial de mensajes directos con {teammate}.<br />Los mensajes directos y archivos que se comparten aquí no son mostrados a personas fuera de esta área.",
"intro_messages.anyMember": " Cualquier miembro se puede unir y leer este canal.",
"intro_messages.beginning": "Inicio de {name}",
@@ -830,6 +879,10 @@
"login_ldap.pwdReq": "La contraseña LDAP es obligatoria",
"login_ldap.signin": "Entrar",
"login_ldap.username": "Usuario LDAP",
+ "login_mfa.enterToken": "Para completar el proceso de inicio de sesión, por favor ingresa el token provisto por el autenticador de tu teléfono inteligente",
+ "login_mfa.submit": "Enviar",
+ "login_mfa.token": "Token AMF",
+ "login_mfa.tokenReq": "Por favor ingresa un token AMF",
"login_username.badTeam": "Mal nombre de equipo",
"login_username.pwd": "Contraseña",
"login_username.pwdReq": "La contraseña es obligatoria",
@@ -873,6 +926,7 @@
"navbar_dropdown.console": "Consola de Sistema",
"navbar_dropdown.create": "Crear nuevo Equipo",
"navbar_dropdown.help": "Ayuda",
+ "navbar_dropdown.integrations": "Integraciones",
"navbar_dropdown.inviteMember": "Invitar Nuevo Miembro",
"navbar_dropdown.logout": "Cerrar sesión",
"navbar_dropdown.manageMembers": "Administrar Miembros",
@@ -888,7 +942,7 @@
"password_form.title": "Restablecer Contraseña",
"password_form.update": "Tu contraseña ha sido actualizada satisfactoriamente.",
"password_send.checkInbox": "Por favor revisa tu bandeja de entrada.",
- "password_send.description": "Para restablecer tu contraseña, ingresa la dirección de correo electrónico que utilizaste para registrarte.",
+ "password_send.description": "Para reiniciar tu contraseña, ingresa la dirección de correo que utilizaste en el registro",
"password_send.email": "Correo electrónico",
"password_send.error": "Por favor ingresa una dirección correo electrónico válida.",
"password_send.link": "<p>Se ha enviado un enlace para restablecer la contraseña a <b>{email}</b></p>",
@@ -985,7 +1039,7 @@
"sidebar.pg": "Grupos Privados",
"sidebar.removeList": "Remover de la lista",
"sidebar.tutorialScreen1": "<h4>Canales</h4><p><strong>Canales</strong> organizan las conversaciones en diferentes tópicos. Son abiertos para cualquier persona de tu equipo. Para enviar comunicaciones privadas con una sola persona utiliza <strong>Mensajes Directos</strong> o con multiples personas utilizando <strong>Grupos Privados</strong>.</p>",
- "sidebar.tutorialScreen2": "<h4>Los canal \"General\" y \"Fuera de Tópico\"</h4><p>Estos son dos canales para comenzar:</p><p><strong>General</strong> es el lugar para tener comunicación con todo el equipo. Todos los integrantes de tu equipo son miembros de este canal.</p><p><strong>Fuera de Tópico</strong> es un lugar para diversión y humor fuera de los canales relacionados con el trabajo. Tu y tu equipo pueden decidir que otros canales crear.</p>",
+ "sidebar.tutorialScreen2": "<h4>Los canal \"{townsquare}\" y \"{offtopic}\"</h4><p>Estos son dos canales para comenzar:</p><p><strong>{townsquare}</strong> es el lugar para tener comunicación con todo el equipo. Todos los integrantes de tu equipo son miembros de este canal.</p><p><strong>{offtopic}</strong> es un lugar para diversión y humor fuera de los canales relacionados con el trabajo. Tu y tu equipo pueden decidir que otros canales crear.</p>",
"sidebar.tutorialScreen3": "<h4>Creando y Uniendose a Canales</h4><p>Pincha en <strong>\"Más...\"</strong> para crear un nuevo canal o unirte a uno existente.</p><p>También puedes crear un nuevo canal o grupo privado al pinchar el simbolo de <strong>\"+\"</strong> que se encuentra al lado del encabezado de Canales o Grupos Privados.</p>",
"sidebar.unreadAbove": "Mensaje(s) sin leer ▲",
"sidebar.unreadBelow": "Mensaje(s) sin leer ▼",
@@ -1027,6 +1081,7 @@
"signup_user_completed.validEmail": "Por favor ingresa una dirección de correo electrónico válida",
"signup_user_completed.welcome": "Bienvenido a:",
"signup_user_completed.whatis": "¿Cuál es tu dirección de correo electrónico?",
+ "signup_user_completed.withLdap": "Con tus credenciales de LDAP",
"sso_signup.find": "Encontrar mi equipo",
"sso_signup.gitlab": "Crea un equipo con una cuenta de GitLab",
"sso_signup.google": "Crea un equipo con una cuenta de Google Apps",
@@ -1133,7 +1188,7 @@
"textbox.quote": ">cita",
"textbox.strike": "tachado",
"tutorial_intro.allSet": "Ya estás listo para comenzar",
- "tutorial_intro.end": "Pincha “Siguiente” para entrar al Canal General. Este es el primer canal que ven tus compañeros cuando ingresan. Utilizalo para mandar mensajes que todos deben leer.",
+ "tutorial_intro.end": "Pincha “Siguiente” para entrar al {channel}. Este es el primer canal que ven tus compañeros cuando ingresan. Utilizalo para mandar mensajes que todos deben leer.",
"tutorial_intro.invite": "Invitar compañeros",
"tutorial_intro.next": "Siguiente",
"tutorial_intro.screenOne": "<h3>Bienvenido a:</h3> <h1>Mattermost</h1> <p>Las comunicaciones de tu equipo en un solo lugar, con búsquedas instantáneas y disponible desde donde sea.</p> <p>Mantén a tu equipo conectado para ayudarlos a conseguir lo que realmente importa.</p>",
@@ -1247,17 +1302,22 @@
"user.settings.general.close": "Cerrar",
"user.settings.general.confirmEmail": "Confirmar Correo electrónico",
"user.settings.general.email": "Correo electrónico",
+ "user.settings.general.emailGitlabCantUpdate": "El inicio de sesión ocurre a través GitLab. El correo electrónico no puede ser actualizado. La dirección de correo electrónico utilizada para las notificaciones es {email}.",
"user.settings.general.emailHelp1": "El correo electrónico es utilizado para iniciar sesión, recibir notificaciones y para restablecer la contraseña. Si se cambia el correo electrónico deberás verificarlo nuevamente.",
"user.settings.general.emailHelp2": "El correo ha sido deshabilitado por el administrador de sistemas. No llegarán correos de notificación hasta que se vuelva a habilitar.",
"user.settings.general.emailHelp3": "El correo electrónico es utilizado para iniciar sesión, recibir notificaciones y para restablecer la contraseña.",
"user.settings.general.emailHelp4": "Un correo de verificación ha sido enviado a {email}.",
+ "user.settings.general.emailLdapCantUpdate": "El inicio de sesión ocurre a través LDAP. El correo electrónico no puede ser actualizado. La dirección de correo electrónico utilizada para las notificaciones es {email}.",
"user.settings.general.emailMatch": "El nuevo correo electrónico introducido no coincide.",
+ "user.settings.general.emptyName": "Pincha 'Editar' para agregar tu nombre completo",
+ "user.settings.general.emptyNickname": "Pincha 'Edita' para agregar un sobrenombre",
"user.settings.general.firstName": "Nombre",
"user.settings.general.fullName": "Nombre completo",
"user.settings.general.imageTooLarge": "No se puede subir la imagen del perfil. El archivo es muy grande.",
"user.settings.general.imageUpdated": "Última actualizacón de la imagen {date}",
"user.settings.general.lastName": "Apellido",
"user.settings.general.loginGitlab": "Inicio de sesión realizado a través de GitLab ({email})",
+ "user.settings.general.loginLdap": "Inicio de sesión realizado a través de LDAP ({email})",
"user.settings.general.newAddress": "Nueva dirección: {email}<br />Revisa tu correo electrónico para verificar tu nueva dirección.",
"user.settings.general.nickname": "Sobrenombre",
"user.settings.general.nicknameExtra": "Utiliza un Sobrenombre por el cual te conocen que sea diferente de tu nombre y del nombre de tu usuario. Esto se utiliza con mayor frecuencia cuando dos o más personas tienen nombres y nombres de usuario que suenan similares.",
@@ -1282,6 +1342,13 @@
"user.settings.integrations.commandsDescription": "Administra tus comandos de barra",
"user.settings.integrations.title": "Configuraciones de Integración",
"user.settings.languages.change": "Cambia el idioma con el que se muestra la intefaz de usuario",
+ "user.settings.mfa.add": "Agrega AMF a tu cuenta",
+ "user.settings.mfa.addHelp": "Para agregar autenticación de múltiples factores a tu cuenta debes tener un teléfono inteligente con Google Authenticator instalado.",
+ "user.settings.mfa.addHelpQr": "Por favor escanea el código QR con la app de Google Authenticator en tu teléfono inteligente e ingresa el token provisto por la app.",
+ "user.settings.mfa.enterToken": "Token",
+ "user.settings.mfa.qrCode": "Código QR",
+ "user.settings.mfa.remove": "Remover AMF de tu cuenta",
+ "user.settings.mfa.removeHelp": "Al remover la autenticación de múltples factores hará que tu cuenta sea vulnerable a ataques.",
"user.settings.modal.advanced": "Avanzada",
"user.settings.modal.confirmBtns": "Sí, Descartar",
"user.settings.modal.confirmMsg": "Tienes cambios sin guardar, ¿Estás seguro que los quieres descartar?",
@@ -1322,18 +1389,22 @@
"user.settings.security.emailPwd": "Correo electrónico y Contraseña",
"user.settings.security.gitlab": "GitLab SSO",
"user.settings.security.lastUpdated": "Última actualización {date} a las {time}",
+ "user.settings.security.loginGitlab": "Inicio de sesión realizado a través de Gitlab",
+ "user.settings.security.loginLdap": "Inicio de sesión realizado a través de LDAP",
"user.settings.security.logoutActiveSessions": "Visualizar y cerrar las sesiones activas",
"user.settings.security.method": "Método de inicio de sesión",
"user.settings.security.newPassword": "Nueva Contraseña",
"user.settings.security.oneSignin": "Sólo puedes tener un método de inicio de sesión a la vez. El cambio del método de inicio de sesión te enviará un correo notificandote que el cambio se realizó con éxito.",
"user.settings.security.password": "Contraseña",
+ "user.settings.security.passwordGitlabCantUpdate": "El inicio de sesión ocurre a través GitLab. La contraseña no se puede actualizar.",
+ "user.settings.security.passwordLdapCantUpdate": "El inicio de sesión ocurre a través LDAP. La contraseña no se puede actualizar.",
"user.settings.security.passwordLengthError": "La nueva contraseña debe contener al menos {chars} carácteres",
"user.settings.security.passwordMatchError": "La nueva contraseña que ingresaste no coincide",
"user.settings.security.retypePassword": "Reescribe la Nueva Contraseña",
"user.settings.security.switchEmail": "Cambiar para utilizar correo electrónico y contraseña",
"user.settings.security.switchGitlab": "Cambiar para utilizar GitLab SSO",
"user.settings.security.switchGoogle": "Cambiar para utilizar Google SSO",
- "user.settings.security.switchLda": "Cambiar a utilizar LDAP",
+ "user.settings.security.switchLdap": "Cambiar a utilizar LDAP",
"user.settings.security.title": "Configuración de Seguridad",
"user.settings.security.viewHistory": "Visualizar historial de acceso",
"user_list.notFound": "No se encontraron usuarios :(",
diff --git a/webapp/i18n/fr.json b/webapp/i18n/fr.json
index f05987e15..be207f0da 100644
--- a/webapp/i18n/fr.json
+++ b/webapp/i18n/fr.json
@@ -986,7 +986,7 @@
"sidebar.pg": "Groupes privés",
"sidebar.removeList": "Retirer de la liste",
"sidebar.tutorialScreen1": "<h4>Canaux</h4><p><strong>Les canaux</strong> organisent les conversations en sujets distincts. Ils sont ouverts à tout le monde dans votre équipe. Pour envoyer des messages privés, utilisez <strong>Messages Privés</strong> pour une personne ou <strong>Groupes Privés</strong> pour plusieurs personnes.</p>",
- "sidebar.tutorialScreen2": "<h4>Canaux \"Town Square\" et \"Off-Topic\"</h4><p>Voici deux canaux publics pour commencer :</p><p><strong>Town Square</strong> (\"centre-ville\") est l'endroit idéal pour communiquer avec toute l'équipe. Tous les membres de votre équipe sont membres de ce canal.</p><p><strong>Off-Topic</strong> (\"hors-sujet\") est l'endroit pour se détendre et parler d'autre chose que de travail. Vous et votre équipe décidez des autres canaux à créer.</p>",
+ "sidebar.tutorialScreen2": "<h4>Canaux \"{townsquare}\" et \"{offtopic}\"</h4><p>Voici deux canaux publics pour commencer :</p><p><strong>{townsquare}</strong> (\"centre-ville\") est l'endroit idéal pour communiquer avec toute l'équipe. Tous les membres de votre équipe sont membres de ce canal.</p><p><strong>{offtopic}</strong> (\"hors-sujet\") est l'endroit pour se détendre et parler d'autre chose que de travail. Vous et votre équipe décidez des autres canaux à créer.</p>",
"sidebar.tutorialScreen3": "<h4>Créer et rejoindre des canaux</h4><p>Cliquez sur <strong>\"Plus...\"</strong> pour créer un nouveau canal ou rejoindre un canal existant.</p><p>Vous pouvez aussi créer un nouveau canal ou un groupe privé en cliquant sur le symbole <strong>\"+\"</strong> à côté du nom du canal ou de l'en-tête du groupe privé.</p>",
"sidebar.unreadAbove": "Message(s) non-lu(s) ci-dessus",
"sidebar.unreadBelow": "Message(s) non-lu(s) ci-dessous",
@@ -1134,7 +1134,7 @@
"textbox.quote": ">citation",
"textbox.strike": "barré",
"tutorial_intro.allSet": "C'est parti !",
- "tutorial_intro.end": "Cliquez sur \"Suivant\" pour entrer dans Town Square. C'est le premier canal que les membres voient quand ils s'inscrivent. Utilisez-le pour poster des messages que tout le monde doit lire en premier.",
+ "tutorial_intro.end": "Cliquez sur \"Suivant\" pour entrer dans {channel}. C'est le premier canal que les membres voient quand ils s'inscrivent. Utilisez-le pour poster des messages que tout le monde doit lire en premier.",
"tutorial_intro.invite": "Inviter des membres",
"tutorial_intro.next": "Suivant",
"tutorial_intro.screenOne": "<h3>Bienvenue sur :</h3><h1>Mattermost</h1><p>Toute la communication de votre équipe à un seul endroit, Your team communication all in one place, instantanément consultable et disponible partout.</p><p>Gardez le lien avec votre équipe pour accomplir les tâches les plus importantes.</p>",
diff --git a/webapp/i18n/pt.json b/webapp/i18n/pt.json
index 466d4b6ea..7525306e6 100644
--- a/webapp/i18n/pt.json
+++ b/webapp/i18n/pt.json
@@ -22,6 +22,28 @@
"activity_log_modal.android": "Android",
"activity_log_modal.androidNativeApp": "App Nativo Android",
"activity_log_modal.iphoneNativeApp": "App Nativo para iPhone",
+ "add_incoming_webhook.cancel": "Cancelar",
+ "add_incoming_webhook.channel": "Canal",
+ "add_incoming_webhook.channelRequired": "Um canal válido é necessário",
+ "add_incoming_webhook.description": "Descrição",
+ "add_incoming_webhook.header": "Adicionar Webhooks Entrada",
+ "add_incoming_webhook.name": "Nome",
+ "add_incoming_webhook.save": "Salvar",
+ "add_integration.header": "Adicionar Integração",
+ "add_integration.incomingWebhook.title": "Webhooks Entrada",
+ "add_integration.incomingWebhook.description": "Criar URLs webhook para usar em integrações externas",
+ "add_integration.outgoingWebhook.title": "Webhooks Saída",
+ "add_integration.outgoingWebhook.description": "Criar webhook para enviar novos eventos de mensagens para uma integração externa.",
+ "add_outgoing_webhook.callbackUrls": "URLs Callback (Uma Por Linha)",
+ "add_outgoing_webhook.callbackUrlsRequired": "Uma ou mais URLs callback são necessárias",
+ "add_outgoing_webhook.cancel": "Cancelar",
+ "add_outgoing_webhook.channel": "Canal",
+ "add_outgoing_webhook.description": "Descrição",
+ "add_outgoing_webhook.header": "Adicionar Webhooks Saída",
+ "add_outgoing_webhook.name": "Nome",
+ "add_outgoing_webhook.save": "Salvar",
+ "add_outgoing_webhook.triggerWOrds": "Palavras Gatilho (Uma Por Linha)",
+ "add_outgoing_webhook.triggerWordsOrChannelRequired": "Um canal válido ou uma lista de palavras gatilho é necessário",
"admin.audits.reload": "Recarregar",
"admin.audits.title": "Atividade de Usuário",
"admin.compliance.directoryDescription": "Diretório o qual os relatórios compliance são gravados, Se estiver em branco, será usado ./data/.",
@@ -306,6 +328,8 @@
"admin.select_team.close": "Fechar",
"admin.select_team.select": "Selecionar",
"admin.select_team.selectTeam": "Selecione Equipe",
+ "admin.service.mfaTitle": "Ativar Autenticação Multi-Fator:",
+ "admin.service.mfaDesc": "Quando verdadeiro, vai ser dada a opção do usuário adicionar autenticação multi-fator em sua conta. Eles irão precisar de um smartphone e um app autenticador como o Google Authenticator.",
"admin.service.attemptDescription": "Tentativas de login permitidas antes que do usuário ser bloqueado e necessário redefinir a senha por e-mail.",
"admin.service.attemptExample": "Ex \"10\"",
"admin.service.attemptTitle": "Máxima Tentativas de Login:",
@@ -550,6 +574,12 @@
"authorize.app": "O app <strong>{appName}</strong> gostaria de ter a capacidade de acessar e modificar suas informações básicas.",
"authorize.deny": "Negar",
"authorize.title": "Um aplicativo gostaria de conectar na sua conta {teamName}",
+ "backstage_navbar.backToMattermost": "Voltar para {siteName}",
+ "backstage_sidebar.integrations": "Integrações",
+ "backstage_sidebar.integrations.installed": "Integrações Instaladas",
+ "backstage_sidebar.integrations.add": "Adicionar Integração",
+ "backstage_sidebar.integrations.add.incomingWebhook": "Webhooks Entrada",
+ "backstage_sidebar.integrations.add.outgoingWebhook": "Webhooks Saída",
"center_panel.recent": "Clique aqui para pular para mensagens recentes. ",
"chanel_header.addMembers": "Adicionar Membros",
"change_url.close": "Fechar",
@@ -626,6 +656,7 @@
"channel_notifications.preferences": "Preferências de Notificação para ",
"channel_notifications.sendDesktop": "Enviar notificações de desktop",
"channel_notifications.unreadInfo": "O nome do canal fica em negrito na barra lateral quando houver mensagens não lidas. Selecionando \"Apenas menções\" o canal vai ficar em negrito apenas quando você for mencionado.",
+ "channel_select.placeholder": "--- Selecione um canal ---",
"choose_auth_page.emailCreate": "Criar uma nova equipe com endereço de email",
"choose_auth_page.find": "Encontrar minhas equipes",
"choose_auth_page.gitlabCreate": "Criar uma equipe com uma conta GitLab",
@@ -768,6 +799,16 @@
"get_team_invite_link_modal.help": "Enviar o link abaixo para sua equipe de trabalho para que eles se inscrevam no site da sua equipe. O Link de Convite de Equipe como ele não muda pode ser compartilhado com várias pessoas ao menos que seja re-gerado em Configurações de Equipe pelo Administrador de Equipe.",
"get_team_invite_link_modal.helpDisabled": "Criação de usuários está desabilitada para sua equipe. Por favor peça ao administrador de equipe por detalhes.",
"get_team_invite_link_modal.title": "Link para Convite de Equipe",
+ "installed_integrations.add": "Adicionar Integração",
+ "installed_integrations.allFilter": "Todos",
+ "installed_integrations.delete": "Deletar",
+ "installed_integrations.header": "Integrações Instaladas",
+ "installed_integrations.incomingWebhooksFilter": "Webhooks Entrada ({count})",
+ "installed_integrations.incomingWebhookType": "(Webhooks Entrada)",
+ "installed_integrations.outgoingWebhooksFilter": "Webhooks Saída ({count})",
+ "installed_integrations.outgoingWebhookType": "(Webhooks Saída)",
+ "installed_integrations.regenToken": "Regen Token",
+ "installed_integrations.search": "Pesquisar Integrações",
"intro_messages.DM": "Este é o início do seu histórico de mensagens diretas com {teammate}.<br />Mensagens diretas e arquivos compartilhados aqui não são mostrados para pessoas de fora desta área.",
"intro_messages.anyMember": " Qualquer membro pode participar e ler este canal.",
"intro_messages.beginning": "Início do {name}",
@@ -818,6 +859,10 @@
"login.session_expired": " Sua sessão expirou. Por favor faça login novamente.",
"login.signTo": "Login em:",
"login.verified": " Email Verificado",
+ "login_mfa.token": "Token MFA",
+ "login_mfa.enterToken": "Para completar o login em processo, por favor entre um token do seu autenticador no smartphone",
+ "login_mfa.submit": "Enviar",
+ "login_mfa.tokenReq": "Por favor entre um token MFA",
"login_email.badTeam": "Nome ruim de equipe",
"login_email.email": "E-mail",
"login_email.emailReq": "Um email é necessário",
@@ -873,6 +918,7 @@
"navbar_dropdown.console": "Console do Sistema",
"navbar_dropdown.create": "Criar uma Nova Equipe",
"navbar_dropdown.help": "Ajuda",
+ "navbar_dropdown.integrations": "Integrações",
"navbar_dropdown.inviteMember": "Convidar Membros da Equipe",
"navbar_dropdown.logout": "Logout",
"navbar_dropdown.manageMembers": "Gerenciar Membros",
@@ -985,7 +1031,7 @@
"sidebar.pg": "Grupos Privados",
"sidebar.removeList": "Remover da lista",
"sidebar.tutorialScreen1": "<h4>Canais</h4><p><strong>Canais</strong> organizar conversas em diferentes tópicos. Eles estão abertos a todos em sua equipe. Para enviar comunicações privadas utilize <strong>Mensagens Diretas</strong> para uma única pessoa ou <strong>Grupos Privados</strong> para várias pessoas.</p>",
- "sidebar.tutorialScreen2": "<h4>Canais \"Town Square\" e \"Off-Topic\"</h4><p>Aqui estão dois canais públicos para começar:</p><p><strong>Town Square</strong> é um lugar comunicação de toda equipe. Todo mundo em sua equipe é um membro deste canal.</p><p><strong>Off-Topic</strong> é um lugar para diversão e humor fora dos canais relacionados com o trabalho. Você e sua equipe podem decidir qual outros canais serão criados.</p>",
+ "sidebar.tutorialScreen2": "<h4>Canais \"{townsquare}\" e \"{offtopic}\"</h4><p>Aqui estão dois canais públicos para começar:</p><p><strong>{townsquare}</strong> é um lugar comunicação de toda equipe. Todo mundo em sua equipe é um membro deste canal.</p><p><strong>{offtopic}</strong> é um lugar para diversão e humor fora dos canais relacionados com o trabalho. Você e sua equipe podem decidir qual outros canais serão criados.</p>",
"sidebar.tutorialScreen3": "<h4>Criando e participando de Canais</h4><p>Clique em <strong>\"Mais...\"</strong> para criar um novo canal ou participar de um já existente.</p><p>Você também pode criar um novo canal ou grupo privado ao clicar em <strong>no símbolo \"+\"</strong> ao lado do canal ou grupo privado no cabeçalho.</p>",
"sidebar.unreadAbove": "Post(s) não lidos abaixo",
"sidebar.unreadBelow": "Post(s) não lidos abaixo",
@@ -1133,7 +1179,7 @@
"textbox.quote": ">citar",
"textbox.strike": "tachado",
"tutorial_intro.allSet": "Está tudo pronto",
- "tutorial_intro.end": "Clique em “Próximo” para entrar Town Square. Este é o primeiro canal que sua equipe de trabalho vê quando eles se inscrevem. Use para postar atualizações que todos precisam saber.",
+ "tutorial_intro.end": "Clique em “Próximo” para entrar {channel}. Este é o primeiro canal que sua equipe de trabalho vê quando eles se inscrevem. Use para postar atualizações que todos precisam saber.",
"tutorial_intro.invite": "Convidar pessoas para equipe",
"tutorial_intro.next": "Próximo",
"tutorial_intro.screenOne": "<h3>Bem vindo ao:</h3><h1>Mattermost</h1><p>Sua equipe de comunicação em um só lugar, pesquisas instantâneas disponível em qualquer lugar</p><p>Mantenha sua equipe conectada para ajudá-los a conseguir o que mais importa.</p>",
@@ -1238,6 +1284,7 @@
"user.settings.display.theme.customTheme": "Tema Customizado",
"user.settings.display.theme.describe": "Abrir para gerenciar seu tema",
"user.settings.display.theme.import": "Importar tema de cores do Slack",
+ "user.settings.display.theme.otherThemes": "Veja outros temas",
"user.settings.display.theme.themeColors": "Tema de Cores",
"user.settings.display.theme.title": "Tema",
"user.settings.display.title": "Configurações de Exibição",
@@ -1246,17 +1293,22 @@
"user.settings.general.close": "Fechar",
"user.settings.general.confirmEmail": "Confirmar o email",
"user.settings.general.email": "E-mail",
+ "user.settings.general.emailGitlabCantUpdate": "Login ocorre através do GitLab. Email não pode ser atualizado. Endereço de email utilizado para notificações é {email}.",
+ "user.settings.general.emailLdapCantUpdate": "Login ocorre através de LDAP. Email não pode ser atualizado. Endereço de email utilizado para notificações é {email}.",
"user.settings.general.emailHelp1": "Email é usado para login, notificações, e redefinição de senha. Requer verificação de email se alterado.",
"user.settings.general.emailHelp2": "Email foi desativado pelo seu administrador de sistema. Nenhuma notificação por email será enviada até isto ser habilitado.",
"user.settings.general.emailHelp3": "Email é usado para login, notificações e redefinição de senha.",
"user.settings.general.emailHelp4": "Uma verificação por email foi enviada para {email}.",
"user.settings.general.emailMatch": "Os novos emails que você inseriu não correspondem.",
+ "user.settings.general.emptyName": "Clique 'Editar' para adicionar seu nome completo",
+ "user.settings.general.emptyNickname": "Clique 'Editar' para adicionar um apelido",
"user.settings.general.firstName": "Primeiro nome",
"user.settings.general.fullName": "Nome Completo",
"user.settings.general.imageTooLarge": "Não é possível fazer upload da imagem de perfil. O arquivo é muito grande.",
"user.settings.general.imageUpdated": "Imagem última atualização {date}",
"user.settings.general.lastName": "Último Nome",
"user.settings.general.loginGitlab": "Login feito através do GitLab ({email})",
+ "user.settings.general.loginLdap": "Login feito através de LDAP ({email})",
"user.settings.general.newAddress": "Novo Endereço: {email}<br />Verifique seu email para checar o endereço acima.",
"user.settings.general.nickname": "Apelido",
"user.settings.general.nicknameExtra": "Use Apelidos para um nome você pode ser chamado assim, isso é diferente de seu primeiro nome e nome de usuário. Este é mais frequentemente usado quando duas ou mais pessoas têm nomes semelhantes de usuário.",
@@ -1321,11 +1373,15 @@
"user.settings.security.emailPwd": "Email e Senha",
"user.settings.security.gitlab": "GitLab SSO",
"user.settings.security.lastUpdated": "Última atualização {date} {time}",
+ "user.settings.security.loginGitlab": "Login feito através do GitLab",
+ "user.settings.security.loginLdap": "Login feito através de LDAP",
"user.settings.security.logoutActiveSessions": "Ver e fazer Logout das Sessões Ativas",
"user.settings.security.method": "Método de Login",
"user.settings.security.newPassword": "Nova Senha",
"user.settings.security.oneSignin": "Você pode ter somente um método de login por vez. Trocando o método de login será enviado um email de notificação se você alterar com sucesso.",
"user.settings.security.password": "Senha",
+ "user.settings.security.passwordGitlabCantUpdate": "Login ocorreu através do GitLab. Senha não pode ser atualizada.",
+ "user.settings.security.passwordLdapCantUpdate": "Login ocorreu através de LDAP. Senha não pode ser atualizada.",
"user.settings.security.passwordLengthError": "Novas senhas precisam ter pelo menos {chars} characters",
"user.settings.security.passwordMatchError": "As novas senhas que você inseriu não correspondem",
"user.settings.security.retypePassword": "Digite Novamente a nova Senha",
@@ -1347,4 +1403,4 @@
"web.footer.terms": "Termos",
"web.header.back": "Voltar",
"web.root.singup_info": "Toda comunicação em um só lugar, pesquisável e acessível em qualquer lugar"
-}
+} \ No newline at end of file
diff --git a/webapp/root.jsx b/webapp/root.jsx
index 112f0880e..417a13659 100644
--- a/webapp/root.jsx
+++ b/webapp/root.jsx
@@ -10,7 +10,7 @@ import 'sass/styles.scss';
import React from 'react';
import ReactDOM from 'react-dom';
-import {Router, Route, IndexRoute, IndexRedirect, browserHistory} from 'react-router';
+import {Router, Route, IndexRoute, IndexRedirect, Redirect, browserHistory} from 'react-router';
import Root from 'components/root.jsx';
import LoggedIn from 'components/logged_in.jsx';
import NotLoggedIn from 'components/not_logged_in.jsx';
@@ -28,6 +28,7 @@ import BrowserStore from 'stores/browser_store.jsx';
import SignupTeam from 'components/signup_team.jsx';
import * as Client from 'utils/client.jsx';
import * as Websockets from 'action_creators/websocket_actions.jsx';
+import * as Utils from 'utils/utils.jsx';
import * as GlobalActions from 'action_creators/global_actions.jsx';
import SignupTeamConfirm from 'components/signup_team_confirm.jsx';
import SignupUserComplete from 'components/signup_user_complete.jsx';
@@ -41,6 +42,7 @@ import InstalledIntegrations from 'components/backstage/installed_integrations.j
import AddIntegration from 'components/backstage/add_integration.jsx';
import AddIncomingWebhook from 'components/backstage/add_incoming_webhook.jsx';
import AddOutgoingWebhook from 'components/backstage/add_outgoing_webhook.jsx';
+import ErrorPage from 'components/error_page.jsx';
import SignupTeamComplete from 'components/signup_team_complete/components/signup_team_complete.jsx';
import WelcomePage from 'components/signup_team_complete/components/team_signup_welcome_page.jsx';
@@ -61,6 +63,13 @@ import Login from 'components/login/login.jsx';
import * as I18n from 'i18n/i18n.jsx';
+const notFoundParams = {
+ title: Utils.localizeMessage('error.not_found.title', 'Page not found'),
+ message: Utils.localizeMessage('error.not_found.message', 'The page you where trying to reach does not exist'),
+ link: '/',
+ linkmessage: Utils.localizeMessage('error.not_found.link_message', 'Back to Mattermost')
+};
+
// This is for anything that needs to be done for ALL react components.
// This runs before we start to render anything.
function preRenderSetup(callwhendone) {
@@ -201,6 +210,10 @@ function renderRootComponent() {
component={Root}
>
<Route
+ path='error'
+ component={ErrorPage}
+ />
+ <Route
component={LoggedIn}
onEnter={preLoggedIn}
>
@@ -267,6 +280,11 @@ function renderRootComponent() {
}}
/>
</Route>
+ <Redirect
+ from='*'
+ to='/error'
+ query={notFoundParams}
+ />
</Route>
<Route
path='admin_console'
@@ -362,6 +380,11 @@ function renderRootComponent() {
component={LDAPToEmail}
/>
</Route>
+ <Redirect
+ from='*'
+ to='/error'
+ query={notFoundParams}
+ />
</Route>
</Route>
</Route>
diff --git a/webapp/sass/layout/_forms.scss b/webapp/sass/layout/_forms.scss
index 259beeb57..1dd2bb827 100644
--- a/webapp/sass/layout/_forms.scss
+++ b/webapp/sass/layout/_forms.scss
@@ -12,7 +12,7 @@
text-align: left;
&.light {
- color: $dark-gray;
+ @include opacity(.6);
font-size: 1.05em;
font-style: italic;
font-weight: normal;
diff --git a/webapp/sass/layout/_post.scss b/webapp/sass/layout/_post.scss
index e2bce5562..947a81318 100644
--- a/webapp/sass/layout/_post.scss
+++ b/webapp/sass/layout/_post.scss
@@ -644,6 +644,15 @@ body.ios {
.post__img {
width: 46px;
+ svg {
+ height: 36px;
+ width: 36px;
+ }
+
+ path {
+ fill: inherit;
+ }
+
img {
@include border-radius(50px);
height: 36px;
diff --git a/webapp/sass/responsive/_desktop.scss b/webapp/sass/responsive/_desktop.scss
index 1ae4b6b70..3b36fb75f 100644
--- a/webapp/sass/responsive/_desktop.scss
+++ b/webapp/sass/responsive/_desktop.scss
@@ -48,6 +48,11 @@
}
}
+ .backstage-content {
+ margin: 46px 46px 46px 150px;
+ }
+
+
.inner-wrap {
&.move--left {
.file-overlay {
diff --git a/webapp/sass/responsive/_mobile.scss b/webapp/sass/responsive/_mobile.scss
index 0e1a471cf..38476485d 100644
--- a/webapp/sass/responsive/_mobile.scss
+++ b/webapp/sass/responsive/_mobile.scss
@@ -1,6 +1,16 @@
@charset 'UTF-8';
@media screen and (max-width: 768px) {
+ .backstage-filters {
+ display: block;
+
+ .backstage-filter__search {
+ border-bottom: 1px solid $light-gray;
+ margin: 10px 0;
+ width: 100%;
+ }
+ }
+
.signup-team__container {
font-size: 1em;
}
@@ -675,9 +685,9 @@
}
.sidebar--right {
- width: 100%;
- right: 0;
@include translate3d(100%, 0, 0);
+ right: 0;
+ width: 100%;
z-index: 5;
&.move--left {
@@ -786,6 +796,40 @@
}
@media screen and (max-width: 640px) {
+ .modal {
+ .about-modal {
+ .about-modal__content {
+ display: block;
+ }
+
+ .about-modal__hash {
+ p {
+ word-break: break-all;
+
+ &:first-child {
+ float: none;
+ }
+ }
+ }
+
+ .about-modal__logo {
+ float: none;
+ padding: 0;
+ text-align: center;
+ width: 100%;
+
+ svg {
+ height: 100px;
+ width: 100px;
+ }
+ }
+
+ .about-modal__logo + div {
+ padding: 2em 0 0;
+ }
+ }
+ }
+
.access-history__table {
> div {
display: block;
@@ -819,6 +863,30 @@
}
@media screen and (max-width: 480px) {
+ .backstage-header {
+ h1 {
+ float: none;
+ margin-bottom: 15px;
+ }
+
+ .add-integrations-link {
+ float: none;
+ }
+ }
+
+ .add-integration {
+ width: 100%;
+ }
+
+ .backstage-list__item {
+ display: block;
+
+ .actions {
+ margin-top: 10px;
+ padding: 0;
+ }
+ }
+
.modal {
.settings-modal {
.settings-table {
diff --git a/webapp/sass/responsive/_tablet.scss b/webapp/sass/responsive/_tablet.scss
index 6863e660b..db2a8d7b9 100644
--- a/webapp/sass/responsive/_tablet.scss
+++ b/webapp/sass/responsive/_tablet.scss
@@ -15,6 +15,19 @@
}
}
+ .backstage-content {
+ margin: 30px;
+ max-width: 100%;
+ padding: 0;
+ }
+
+ .backstage-sidebar {
+ height: auto;
+ padding: 30px 15px 0;
+ position: relative;
+ width: 100%;
+ }
+
.help__format-text {
display: none;
}
diff --git a/webapp/sass/routes/_about-modal.scss b/webapp/sass/routes/_about-modal.scss
new file mode 100644
index 000000000..98119c8aa
--- /dev/null
+++ b/webapp/sass/routes/_about-modal.scss
@@ -0,0 +1,78 @@
+@charset 'UTF-8';
+
+.modal {
+ .about-modal {
+ .modal-header {
+ background: transparent;
+ border: none;
+ color: inherit;
+ padding: 20px 25px 0;
+
+ .close {
+ color: inherit;
+ font-weight: normal;
+ right: 15px;
+ }
+
+ .modal-title {
+ color: inherit;
+ font-size: 16px;
+ }
+ }
+
+ .modal-body {
+ padding: 20px 25px 5px;
+ }
+
+ .about-modal__content {
+ @include clearfix;
+ @include display-flex;
+ @include flex-direction(row);
+ padding: 1em 0 3em;
+ }
+
+ .about-modal__copyright {
+ @include opacity(.6);
+ margin-top: .5em;
+ }
+
+ .about-modal__footer {
+ font-size: 13.5px;
+ }
+
+ .about-modal__title {
+ line-height: 1.5;
+ margin: 0 0 10px;
+ }
+
+ .about-modal__subtitle {
+ @include opacity(.6);
+ }
+
+ .about-modal__hash {
+ @include opacity(.4);
+ font-size: .75em;
+ text-align: right;
+
+ p {
+ &:first-child {
+ float: left;
+ }
+ }
+ }
+
+ .about-modal__logo {
+ @include opacity(.9);
+ padding: 0 40px 0 20px;
+
+ svg {
+ height: 125px;
+ width: 125px;
+ }
+
+ path {
+ fill: inherit;
+ }
+ }
+ }
+}
diff --git a/webapp/sass/routes/_backstage.scss b/webapp/sass/routes/_backstage.scss
index 70bab99cb..729c8c912 100644
--- a/webapp/sass/routes/_backstage.scss
+++ b/webapp/sass/routes/_backstage.scss
@@ -1,103 +1,106 @@
-.backstage {
- background-color: #f2f2f2;
+.backstage-content {
+ background-color: $bg--gray;
height: 100%;
- padding-left: 260px;
- padding-top: 45px;
+ margin: 46px auto;
+ max-width: 960px;
+ padding-left: 135px;
}
-.backstage__navbar {
- background: white;
- border-bottom: lightgray 1px solid;
- margin: 0 -15px;
- padding: 10px;
+.backstage-navbar {
+ background: $white;
+ border-bottom: 1px solid $light-gray;
+ padding: 10px 20px;
z-index: 10;
}
-.backstage__navbar__back {
- color: black;
+.backstage-navbar__back {
+ color: inherit;
+ text-decoration: none;
.fa {
+ font-size: 1.1em;
font-weight: bold;
- margin-right: 5px;
+ margin-right: 7px;
+ }
+
+ &:hover,
+ &:active {
+ color: inherit;
}
}
-.backstage__sidebar {
- position: absolute;
+.backstage-sidebar {
+ height: 100%;
left: 0;
+ padding: 50px 20px;
+ position: absolute;
width: 260px;
- height: 100%;
- background-color: #f2f2f2;
- padding-bottom: 20px;
- padding-left: 20px;
- padding-right: 20px;
- padding-top: 45px;
z-index: 5;
ul {
- padding: 0px;
list-style: none;
+ padding: 0;
}
}
-.backstage__sidebar__category {
- border: lightgray 1px solid;
+.backstage-sidebar__category {
+ border: 1px solid $light-gray;
.category-title {
- color: gray;
display: block;
- padding: 5px 10px;
+ line-height: 36px;
+ padding: 0 10px;
position: relative;
}
.category-title--active {
- color: black;
+ color: $black;
}
.category-title__text {
- position: absolute;
left: 2em;
+ position: absolute;
}
.sections {
- background: white;
- border-top: lightgray 1px solid;
+ background: $white;
+ border-top: 1px solid $light-gray;
}
- .section-title {
+ .section-title,
+ .subsection-title {
display: block;
+ font-size: .95em;
+ line-height: 29px;
padding-left: 2em;
- }
-
- .subsection {
+ text-decoration: none;
}
.subsection-title {
- display: block;
padding-left: 3em;
}
- .section-title--active, .subsection-title--active {
- background-color:#2388d6;
- color: white;
+ .section-title--active,
+ .subsection-title--active {
+ background-color: $primary-color;
+ color: $white;
+ font-weight: 600;
}
}
.backstage__sidebar__category + .backstage__sidebar__category {
- border-top-width: 0px;
+ border-top-width: 0;
}
-.installed-integrations {
- height: 100%;
- max-width: 958px;
-}
-
-.backstage__header {
+.backstage-header {
+ @include clearfix;
margin-bottom: 20px;
width: 100%;
- .text {
- display: inline;
+ h1 {
+ float: left;
+ font-size: 20px;
+ margin: 5px 0;
}
.add-integrations-link {
@@ -105,21 +108,22 @@
}
}
-.installed-integrations__filters {
+.backstage-filters {
display: flex;
flex-direction: row;
- margin-bottom: 8px;
width: 100%;
- .type-filters {
+ .backstage-filters__sort {
flex-grow: 1;
flex-shrink: 0;
+ line-height: 30px;
- .type-filter {
- &--selected {
- color: black;
+ .filter-sort {
+ text-decoration: none;
+
+ &.filter-sort--active {
+ color: inherit;
cursor: default;
- font-weight: bold;
}
}
@@ -129,84 +133,135 @@
}
}
- .filter-box {
+ .backstage-filter__search {
flex-grow: 0;
flex-shrink: 0;
+ position: relative;
+ width: 270px;
+
+ .fa {
+ @include opacity(.4);
+ left: 11px;
+ position: absolute;
+ top: 11px;
+ }
+
+ input {
+ background: $white;
+ border-bottom: none;
+ padding-left: 30px;
+ }
}
}
-.installed-integrations__list {
- background-color: white;
- border: lightgray 1px solid;
- padding-bottom: 30px;
- padding-left: 30px;
- padding-right: 30px;
- padding-top: 10px;
+.backstage-list {
+ background-color: $white;
+ border: 1px solid $light-gray;
+ padding: 5px 15px;
}
-.installed-integrations__item {
- border-bottom: lightgray 1px solid;
+.backstage-list__item {
+ border-bottom: 1px solid $light-gray;
display: flex;
- padding: 20px;
+ padding: 20px 15px;
+
+ &:last-child {
+ border: none;
+ }
- .details {
+ .item-details {
flex-grow: 1;
flex-shrink: 1;
overflow: hidden;
text-overflow: ellipsis;
+ }
- .details-row + .details-row {
- margin-top: 15px;
- }
+ .item-details__row + .item-details__row {
+ @include clearfix;
+ margin-top: 10px;
+ text-overflow: ellipsis;
+ }
- .name {
- font-weight: bold;
- margin-bottom: 1em;
- }
+ .item-details__name {
+ font-weight: 600;
+ margin-bottom: 1em;
+ }
- .type {
- margin-left: 6px;
- }
+ .item-details__type {
+ margin-left: 6px;
+ }
- .description {
- color: gray;
- margin-bottom: 1em;
- }
+ .item-details__description {
+ color: $dark-gray;
+ margin-bottom: 1em;
}
- .actions {
+ .list-item__actions {
flex-grow: 0;
flex-shrink: 0;
padding-left: 20px;
}
}
-.add-integration-option {
- background-color: white;
- border: lightgray 1px solid;
+// Backstage Form
+
+.backstage-form {
+ background-color: $white;
+ border: 1px solid $light-gray;
+ padding: 40px 30px 30px;
+
+ label {
+ font-weight: normal;
+ }
+
+ .form-control {
+ background: $white;
+
+ &:focus {
+ border-color: $primary-color;
+ }
+ }
+}
+
+.backstage-form__footer {
+ border-top: 1px solid $light-gray;
+ margin-top: 2.5em;
+ padding-top: 1.8em;
+ text-align: right;
+
+ .has-error {
+ float: left;
+ margin: 0;
+ }
+}
+
+.add-integration {
+ background-color: $white;
+ border: 1px solid $light-gray;
display: inline-block;
height: 210px;
- margin-right: 30px;
+ margin: 0 30px 20px 0;
padding: 20px;
text-align: center;
+ vertical-align: top;
width: 250px;
&:hover {
color: default;
text-decoration: none;
}
+}
- &__image {
- width: 80px;
- height: 80px;
- }
+.add-integration__image {
+ height: 80px;
+ width: 80px;
+}
- &__title {
- color: black;
- margin-bottom: 10px;
- }
+.add-integration__title {
+ color: $black;
+ margin-bottom: 10px;
+}
- &__description {
- color: gray;
- }
+.add-integration__description {
+ color: $dark-gray;
}
diff --git a/webapp/sass/routes/_module.scss b/webapp/sass/routes/_module.scss
index b76dd147f..4f3f6f9cd 100644
--- a/webapp/sass/routes/_module.scss
+++ b/webapp/sass/routes/_module.scss
@@ -1,4 +1,5 @@
// Only for combining all the files in this folder
+@import 'about-modal';
@import 'access-history';
@import 'activity-log';
@import 'admin-console';
diff --git a/webapp/sass/utils/_variables.scss b/webapp/sass/utils/_variables.scss
index 345ab11e8..53004520e 100644
--- a/webapp/sass/utils/_variables.scss
+++ b/webapp/sass/utils/_variables.scss
@@ -8,7 +8,7 @@ $white: rgb(255, 255, 255);
$black: rgb(0, 0, 0);
$red: rgb(229, 101, 101);
$yellow: rgb(255, 255, 0);
-$light-gray: rgba(0, 0, 0, .06);
+$light-gray: rgba(0, 0, 0, .15);
$gray: rgba(0, 0, 0, .3);
$dark-gray: rgba(0, 0, 0, .5);
diff --git a/webapp/stores/post_store.jsx b/webapp/stores/post_store.jsx
index f328ca306..3f2f75796 100644
--- a/webapp/stores/post_store.jsx
+++ b/webapp/stores/post_store.jsx
@@ -96,7 +96,7 @@ class PostStoreClass extends EventEmitter {
let post = null;
if (posts.posts.hasOwnProperty(postId)) {
- post = Object.assign({}, posts.posts[postId]);
+ post = posts.posts[postId];
}
return post;
@@ -104,7 +104,7 @@ class PostStoreClass extends EventEmitter {
getAllPosts(id) {
if (this.postsInfo.hasOwnProperty(id)) {
- return Object.assign({}, this.postsInfo[id].postList);
+ return this.postsInfo[id].postList;
}
return null;
diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx
index 68df771f9..d01163b31 100644
--- a/webapp/utils/constants.jsx
+++ b/webapp/utils/constants.jsx
@@ -192,7 +192,9 @@ export default {
MOBILE_VIDEO_WIDTH: 480,
MOBILE_VIDEO_HEIGHT: 360,
DEFAULT_CHANNEL: 'town-square',
+ DEFAULT_CHANNEL_UI_NAME: 'Town Square',
OFFTOPIC_CHANNEL: 'off-topic',
+ OFFTOPIC_CHANNEL_UI_NAME: 'Off-Topic',
GITLAB_SERVICE: 'gitlab',
GOOGLE_SERVICE: 'google',
EMAIL_SERVICE: 'email',
@@ -246,6 +248,7 @@ export default {
OPEN_TEAM: 'O',
MAX_POST_LEN: 4000,
EMOJI_SIZE: 16,
+ MATTERMOST_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'viewBox='0 0 500 500' style='enable-background:new 0 0 500 500;' xml:space='preserve'> <style type='text/css'> .st0{fill-rule:evenodd;clip-rule:evenodd;fill:#222222;} </style> <g id='XMLID_1_'> <g id='XMLID_3_'> <path id='XMLID_4_' class='st0' d='M396.9,47.7l2.6,53.1c43,47.5,60,114.8,38.6,178.1c-32,94.4-137.4,144.1-235.4,110.9 S51.1,253.1,83,158.7C104.5,95.2,159.2,52,222.5,40.5l34.2-40.4C150-2.8,49.3,63.4,13.3,169.9C-31,300.6,39.1,442.5,169.9,486.7 s272.6-25.8,316.9-156.6C522.7,223.9,483.1,110.3,396.9,47.7z'/> </g> <path id='XMLID_2_' class='st0' d='M335.6,204.3l-1.8-74.2l-1.5-42.7l-1-37c0,0,0.2-17.8-0.4-22c-0.1-0.9-0.4-1.6-0.7-2.2 c0-0.1-0.1-0.2-0.1-0.3c0-0.1-0.1-0.2-0.1-0.2c-0.7-1.2-1.8-2.1-3.1-2.6c-1.4-0.5-2.9-0.4-4.2,0.2c0,0-0.1,0-0.1,0 c-0.2,0.1-0.3,0.1-0.4,0.2c-0.6,0.3-1.2,0.7-1.8,1.3c-3,3-13.7,17.2-13.7,17.2l-23.2,28.8l-27.1,33l-46.5,57.8 c0,0-21.3,26.6-16.6,59.4s29.1,48.7,48,55.1c18.9,6.4,48,8.5,71.6-14.7C336.4,238.4,335.6,204.3,335.6,204.3z'/> </g> </svg>",
ONLINE_ICON_SVG: "<svg version='1.1'id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:cc='http://creativecommons.org/ns#' inkscape:version='0.48.4 r9939' sodipodi:docname='TRASH_1_4.svg'xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='-243 245 12 12'style='enable-background:new -243 245 12 12;' xml:space='preserve'> <sodipodi:namedview inkscape:cx='26.358185' inkscape:zoom='1.18' bordercolor='#666666' pagecolor='#ffffff' borderopacity='1' objecttolerance='10' inkscape:cy='139.7898' gridtolerance='10' guidetolerance='10' showgrid='false' showguides='true' id='namedview6' inkscape:pageopacity='0' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:window-width='1366' inkscape:current-layer='Layer_1' inkscape:window-height='705' inkscape:window-y='-8' inkscape:window-maximized='1' inkscape:window-x='-8'> <sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide> <sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide> </sodipodi:namedview> <g> <path class='online--icon' d='M-236,250.5C-236,250.5-236,250.5-236,250.5C-236,250.5-236,250.5-236,250.5C-236,250.5-236,250.5-236,250.5z'/> <ellipse class='online--icon' cx='-238.5' cy='248' rx='2.5' ry='2.5'/> </g> <path class='online--icon' d='M-238.9,253.8c0-0.4,0.1-0.9,0.2-1.3c-2.2-0.2-2.2-2-2.2-2s-1,0.1-1.2,0.5c-0.4,0.6-0.6,1.7-0.7,2.5c0,0.1-0.1,0.5,0,0.6 c0.2,1.3,2.2,2.3,4.4,2.4c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0C-238.7,255.7-238.9,254.8-238.9,253.8z'/> <g> <g> <path class='online--icon' d='M-232.3,250.1l1.3,1.3c0,0,0,0.1,0,0.1l-4.1,4.1c0,0,0,0-0.1,0c0,0,0,0,0,0l-2.7-2.7c0,0,0-0.1,0-0.1l1.2-1.2 c0,0,0.1,0,0.1,0l1.4,1.4l2.9-2.9C-232.4,250.1-232.3,250.1-232.3,250.1z'/> </g> </g> </svg>",
AWAY_ICON_SVG: "<svg version='1.1'id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:cc='http://creativecommons.org/ns#' inkscape:version='0.48.4 r9939' sodipodi:docname='TRASH_1_4.svg'xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='-299 391 12 12'style='enable-background:new -299 391 12 12;' xml:space='preserve'> <sodipodi:namedview inkscape:cx='26.358185' inkscape:zoom='1.18' bordercolor='#666666' pagecolor='#ffffff' borderopacity='1' objecttolerance='10' inkscape:cy='139.7898' gridtolerance='10' guidetolerance='10' showgrid='false' showguides='true' id='namedview6' inkscape:pageopacity='0' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:window-width='1366' inkscape:current-layer='Layer_1' inkscape:window-height='705' inkscape:window-y='-8' inkscape:window-maximized='1' inkscape:window-x='-8'> <sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide> <sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide> </sodipodi:namedview> <g> <ellipse class='away--icon' cx='-294.6' cy='394' rx='2.5' ry='2.5'/> <path class='away--icon' d='M-293.8,399.4c0-0.4,0.1-0.7,0.2-1c-0.3,0.1-0.6,0.2-1,0.2c-2.5,0-2.5-2-2.5-2s-1,0.1-1.2,0.5c-0.4,0.6-0.6,1.7-0.7,2.5 c0,0.1-0.1,0.5,0,0.6c0.2,1.3,2.2,2.3,4.4,2.4c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0c0.7,0,1.4-0.1,2-0.3 C-293.3,401.5-293.8,400.5-293.8,399.4z'/> </g> <path class='away--icon' d='M-287,400c0,0.1-0.1,0.1-0.1,0.1l-4.9,0c-0.1,0-0.1-0.1-0.1-0.1v-1.6c0-0.1,0.1-0.1,0.1-0.1l4.9,0c0.1,0,0.1,0.1,0.1,0.1 V400z'/> </svg>",
OFFLINE_ICON_SVG: "<svg version='1.1'id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:cc='http://creativecommons.org/ns#' inkscape:version='0.48.4 r9939' sodipodi:docname='TRASH_1_4.svg'xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='-299 391 12 12'style='enable-background:new -299 391 12 12;' xml:space='preserve'> <sodipodi:namedview inkscape:cx='26.358185' inkscape:zoom='1.18' bordercolor='#666666' pagecolor='#ffffff' borderopacity='1' objecttolerance='10' inkscape:cy='139.7898' gridtolerance='10' guidetolerance='10' showgrid='false' showguides='true' id='namedview6' inkscape:pageopacity='0' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:window-width='1366' inkscape:current-layer='Layer_1' inkscape:window-height='705' inkscape:window-y='-8' inkscape:window-maximized='1' inkscape:window-x='-8'> <sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide> <sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide> </sodipodi:namedview> <g> <g> <ellipse class='offline--icon' cx='-294.5' cy='394' rx='2.5' ry='2.5'/> <path class='offline--icon' d='M-294.3,399.7c0-0.4,0.1-0.8,0.2-1.2c-0.1,0-0.2,0-0.4,0c-2.5,0-2.5-2-2.5-2s-1,0.1-1.2,0.5c-0.4,0.6-0.6,1.7-0.7,2.5 c0,0.1-0.1,0.5,0,0.6c0.2,1.3,2.2,2.3,4.4,2.4h0.1h0.1c0.3,0,0.7,0,1-0.1C-293.9,401.6-294.3,400.7-294.3,399.7z'/> </g> </g> <g> <path class='offline--icon' d='M-288.9,399.4l1.8-1.8c0.1-0.1,0.1-0.3,0-0.3l-0.7-0.7c-0.1-0.1-0.3-0.1-0.3,0l-1.8,1.8l-1.8-1.8c-0.1-0.1-0.3-0.1-0.3,0 l-0.7,0.7c-0.1,0.1-0.1,0.3,0,0.3l1.8,1.8l-1.8,1.8c-0.1,0.1-0.1,0.3,0,0.3l0.7,0.7c0.1,0.1,0.3,0.1,0.3,0l1.8-1.8l1.8,1.8 c0.1,0.1,0.3,0.1,0.3,0l0.7-0.7c0.1-0.1,0.1-0.3,0-0.3L-288.9,399.4z'/> </g> </svg>",
diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx
index b248368fc..3b7583f15 100644
--- a/webapp/utils/utils.jsx
+++ b/webapp/utils/utils.jsx
@@ -14,7 +14,6 @@ var ActionTypes = Constants.ActionTypes;
import * as Client from './client.jsx';
import * as AsyncClient from './async_client.jsx';
import * as client from './client.jsx';
-import Autolinker from 'autolinker';
import React from 'react';
import {browserHistory} from 'react-router';
@@ -314,14 +313,8 @@ export function getTimestamp() {
}
// extracts links not styled by Markdown
-export function extractLinks(text) {
- text; // eslint-disable-line no-unused-expressions
- Autolinker; // eslint-disable-line no-unused-expressions
-
- // skip this operation because autolinker is having issues
- return [];
-
- /*const links = [];
+export function extractFirstLink(text) {
+ const pattern = /(^|[\s\n]|<br\/?>)((?:https?|ftp):\/\/[\-A-Z0-9+\u0026\u2019@#\/%?=()~_|!:,.;]*[\-A-Z0-9+\u0026@#\/%=~()_|])/i;
let inText = text;
// strip out code blocks
@@ -330,32 +323,12 @@ export function extractLinks(text) {
// strip out inline markdown images
inText = inText.replace(/!\[[^\]]*\]\([^\)]*\)/g, '');
- function replaceFn(autolinker, match) {
- let link = '';
- const matchText = match.getMatchedText();
-
- if (matchText.trim().indexOf('http') === 0) {
- link = matchText;
- } else {
- link = 'http://' + matchText;
- }
-
- links.push(link);
+ const match = pattern.exec(inText);
+ if (match) {
+ return match[0].trim();
}
- Autolinker.link(
- inText,
- {
- replaceFn,
- urls: {schemeMatches: true, wwwMatches: true, tldMatches: false},
- emails: false,
- twitter: false,
- phone: false,
- hashtag: false
- }
- );
-
- return links;*/
+ return '';
}
export function escapeRegExp(string) {
@@ -756,6 +729,7 @@ export function applyTheme(theme) {
changeCss('.search-help-popover .search-autocomplete__item.selected', 'background:' + changeOpacity(theme.centerChannelColor, 0.15), 1);
changeCss('::-webkit-scrollbar-thumb', 'background:' + changeOpacity(theme.centerChannelColor, 0.4), 1);
changeCss('body', 'scrollbar-arrow-color:' + theme.centerChannelColor, 4);
+ changeCss('.modal .about-modal .about-modal__logo svg, .post .post__img svg', 'fill:' + theme.centerChannelColor, 1);
}
if (theme.newMessageSeparator) {
@@ -1405,3 +1379,18 @@ export function localizeMessage(id, defaultMessage) {
return id;
}
+
+export function getProfilePicSrcForPost(post, timestamp) {
+ let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp;
+ if (post.props && post.props.from_webhook && global.window.mm_config.EnablePostIconOverride === 'true') {
+ if (post.props.override_icon_url) {
+ src = post.props.override_icon_url;
+ } else {
+ src = Constants.DEFAULT_WEBHOOK_LOGO;
+ }
+ } else if (isSystemMessage(post)) {
+ src = Constants.SYSTEM_MESSAGE_PROFILE_IMAGE;
+ }
+
+ return src;
+}
diff --git a/webapp/webpack.config.js b/webapp/webpack.config.js
index 478c5de81..4e2d6b70d 100644
--- a/webapp/webpack.config.js
+++ b/webapp/webpack.config.js
@@ -77,7 +77,9 @@ var config = {
}),
htmlExtract,
new CopyWebpackPlugin([
- {from: 'images/emoji', to: 'emoji'}
+ {from: 'images/emoji', to: 'emoji'},
+ {from: 'images/logo-email.png', to: 'images'},
+ {from: 'images/circles.png', to: 'images'}
]),
new webpack.LoaderOptionsPlugin({
minimize: !DEV,