summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoramWilander <jwawilander@gmail.com>2016-06-13 15:29:24 -0400
committerJoramWilander <jwawilander@gmail.com>2016-06-13 15:29:24 -0400
commit5af526c259d9c6477d67eb8f8a1e7b86270c7294 (patch)
tree97cc595107026a1f19d6fd6e35627dda23979aa3
parentbb5ca5a8780f9259306d881e85760de8b87ddea8 (diff)
parent974238231b9cdbd39a825ec8e9299fbb0b51f6b8 (diff)
downloadchat-5af526c259d9c6477d67eb8f8a1e7b86270c7294.tar.gz
chat-5af526c259d9c6477d67eb8f8a1e7b86270c7294.tar.bz2
chat-5af526c259d9c6477d67eb8f8a1e7b86270c7294.zip
Merge branch 'release-3.1'
Conflicts: webapp/components/create_comment.jsx
-rw-r--r--api/oauth.go64
-rw-r--r--api/team.go12
-rw-r--r--api/user.go23
-rw-r--r--mattermost.go1
-rw-r--r--model/config.go100
-rw-r--r--model/version.go1
-rw-r--r--store/sql_team_store.go8
-rw-r--r--webapp/actions/global_actions.jsx4
-rw-r--r--webapp/components/admin_console/admin_sidebar.jsx27
-rw-r--r--webapp/components/admin_console/ldap_settings.jsx2
-rw-r--r--webapp/components/admin_console/multiselect_settings.jsx1
-rw-r--r--webapp/components/admin_console/storage_settings.jsx41
-rw-r--r--webapp/components/analytics/doughnut_chart.jsx12
-rw-r--r--webapp/components/analytics/line_chart.jsx12
-rw-r--r--webapp/components/create_comment.jsx2
-rw-r--r--webapp/components/create_post.jsx2
-rw-r--r--webapp/components/edit_post_modal.jsx1
-rw-r--r--webapp/components/login/login_controller.jsx8
-rw-r--r--webapp/components/rhs_root_post.jsx1
-rw-r--r--webapp/components/signup_user_complete.jsx34
-rw-r--r--webapp/i18n/en.json4
-rw-r--r--webapp/i18n/es.json2
-rw-r--r--webapp/i18n/i18n.jsx1
-rw-r--r--webapp/package.json2
-rw-r--r--webapp/sass/components/_mentions.scss8
-rw-r--r--webapp/stores/post_store.jsx2
-rw-r--r--webapp/stores/user_store.jsx2
-rw-r--r--webapp/utils/async_client.jsx10
-rw-r--r--webapp/utils/markdown.jsx2
29 files changed, 252 insertions, 137 deletions
diff --git a/api/oauth.go b/api/oauth.go
index 30efbdce3..072699321 100644
--- a/api/oauth.go
+++ b/api/oauth.go
@@ -204,7 +204,10 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
}
break
case model.OAUTH_ACTION_LOGIN:
- LoginByOAuth(c, w, r, service, body)
+ user := LoginByOAuth(c, w, r, service, body)
+ if len(teamId) > 0 {
+ c.Err = JoinUserToTeamById(teamId, user)
+ }
if c.Err == nil {
http.Redirect(w, r, GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
}
@@ -424,8 +427,17 @@ func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
service := params["service"]
loginHint := r.URL.Query().Get("login_hint")
+ teamId, err := getTeamIdFromQuery(r.URL.Query())
+ if err != nil {
+ c.Err = err
+ return
+ }
+
stateProps := map[string]string{}
stateProps["action"] = model.OAUTH_ACTION_LOGIN
+ if len(teamId) != 0 {
+ stateProps["team_id"] = teamId
+ }
if authUrl, err := GetAuthorizationCode(c, service, stateProps, loginHint); err != nil {
c.Err = err
@@ -435,46 +447,52 @@ func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
-func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
- params := mux.Vars(r)
- service := params["service"]
-
- if !utils.Cfg.TeamSettings.EnableUserCreation {
- c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.disabled.app_error", nil, "")
- c.Err.StatusCode = http.StatusNotImplemented
- return
- }
-
- hash := r.URL.Query().Get("h")
-
- teamId := ""
- inviteId := r.URL.Query().Get("id")
+func getTeamIdFromQuery(query url.Values) (string, *model.AppError) {
+ hash := query.Get("h")
+ inviteId := query.Get("id")
if len(hash) > 0 {
- data := r.URL.Query().Get("d")
+ data := query.Get("d")
props := model.MapFromJson(strings.NewReader(data))
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
- c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.invalid_link.app_error", nil, "")
- return
+ return "", model.NewLocAppError("getTeamIdFromQuery", "web.singup_with_oauth.invalid_link.app_error", nil, "")
}
t, err := strconv.ParseInt(props["time"], 10, 64)
if err != nil || model.GetMillis()-t > 1000*60*60*48 { // 48 hours
- c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.expired_link.app_error", nil, "")
- return
+ return "", model.NewLocAppError("getTeamIdFromQuery", "web.singup_with_oauth.expired_link.app_error", nil, "")
}
- teamId = props["id"]
- } else if len(inviteId) != 0 {
+ return props["id"], nil
+ } else if len(inviteId) > 0 {
if result := <-Srv.Store.Team().GetByInviteId(inviteId); result.Err != nil {
// soft fail, so we still create user but don't auto-join team
l4g.Error("%v", result.Err)
} else {
- teamId = result.Data.(*model.Team).Id
+ return result.Data.(*model.Team).Id, nil
}
}
+ return "", nil
+}
+
+func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
+ params := mux.Vars(r)
+ service := params["service"]
+
+ if !utils.Cfg.TeamSettings.EnableUserCreation {
+ c.Err = model.NewLocAppError("signupWithOAuth", "web.singup_with_oauth.disabled.app_error", nil, "")
+ c.Err.StatusCode = http.StatusNotImplemented
+ return
+ }
+
+ teamId, err := getTeamIdFromQuery(r.URL.Query())
+ if err != nil {
+ c.Err = err
+ return
+ }
+
stateProps := map[string]string{}
stateProps["action"] = model.OAUTH_ACTION_SIGNUP
if len(teamId) != 0 {
diff --git a/api/team.go b/api/team.go
index 8eb7c4fef..46bff617b 100644
--- a/api/team.go
+++ b/api/team.go
@@ -17,6 +17,7 @@ import (
"github.com/gorilla/mux"
"github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
)
@@ -247,6 +248,14 @@ func CreateTeam(c *Context, team *model.Team) *model.Team {
}
}
+func JoinUserToTeamById(teamId string, user *model.User) *model.AppError {
+ if result := <-Srv.Store.Team().Get(teamId); result.Err != nil {
+ return result.Err
+ } else {
+ return JoinUserToTeam(result.Data.(*model.Team), user)
+ }
+}
+
func JoinUserToTeam(team *model.Team, user *model.User) *model.AppError {
tm := &model.TeamMember{TeamId: team.Id, UserId: user.Id}
@@ -258,6 +267,9 @@ func JoinUserToTeam(team *model.Team, user *model.User) *model.AppError {
}
if tmr := <-Srv.Store.Team().SaveMember(tm); tmr.Err != nil {
+ if tmr.Err.Id == store.TEAM_MEMBER_EXISTS_ERROR {
+ return nil
+ }
return tmr.Err
}
diff --git a/api/user.go b/api/user.go
index de7a560bf..aae3dffa5 100644
--- a/api/user.go
+++ b/api/user.go
@@ -285,11 +285,6 @@ func CreateOAuthUser(c *Context, w http.ResponseWriter, r *http.Request, service
suchan := Srv.Store.User().GetByAuth(user.AuthData, service)
euchan := Srv.Store.User().GetByEmail(user.Email)
- var tchan store.StoreChannel
- if len(teamId) != 0 {
- tchan = Srv.Store.Team().Get(teamId)
- }
-
found := true
count := 0
for found {
@@ -319,20 +314,14 @@ func CreateOAuthUser(c *Context, w http.ResponseWriter, r *http.Request, service
return nil
}
- if tchan != nil {
- if result := <-tchan; result.Err != nil {
- c.Err = result.Err
+ if len(teamId) > 0 {
+ err = JoinUserToTeamById(teamId, user)
+ if err != nil {
+ c.Err = err
return nil
- } else {
- team := result.Data.(*model.Team)
- err = JoinUserToTeam(team, user)
- if err != nil {
- c.Err = err
- return nil
- }
-
- go addDirectChannels(team.Id, user)
}
+
+ go addDirectChannels(teamId, user)
}
doLogin(c, w, r, ruser, "")
diff --git a/mattermost.go b/mattermost.go
index d6353c14a..67428275a 100644
--- a/mattermost.go
+++ b/mattermost.go
@@ -868,6 +868,7 @@ func cmdVersion() {
fmt.Fprintln(os.Stderr, "Build Date: "+model.BuildDate)
fmt.Fprintln(os.Stderr, "Build Hash: "+model.BuildHash)
fmt.Fprintln(os.Stderr, "Build Enterprise Ready: "+model.BuildEnterpriseReady)
+ fmt.Fprintln(os.Stderr, "DB Version: "+api.Srv.Store.(*store.SqlStore).SchemaVersion)
os.Exit(0)
}
diff --git a/model/config.go b/model/config.go
index b9177204b..08510fc44 100644
--- a/model/config.go
+++ b/model/config.go
@@ -6,6 +6,7 @@ package model
import (
"encoding/json"
"io"
+ "strings"
)
const (
@@ -35,6 +36,15 @@ const (
FAKE_SETTING = "********************************"
)
+// should match the values in webapp/i18n/i18n.jsx
+var LOCALES = []string{
+ "en",
+ "es",
+ "fr",
+ "ja",
+ "pt-BR",
+}
+
type ServiceSettings struct {
ListenAddress string
MaximumLoginAttempts int
@@ -420,19 +430,39 @@ func (o *Config) SetDefaults() {
*o.SupportSettings.SupportEmail = "feedback@mattermost.com"
}
+ if o.LdapSettings.Enable == nil {
+ o.LdapSettings.Enable = new(bool)
+ *o.LdapSettings.Enable = false
+ }
+
+ if o.LdapSettings.LdapServer == nil {
+ o.LdapSettings.LdapServer = new(string)
+ *o.LdapSettings.LdapServer = ""
+ }
+
if o.LdapSettings.LdapPort == nil {
o.LdapSettings.LdapPort = new(int)
*o.LdapSettings.LdapPort = 389
}
- if o.LdapSettings.QueryTimeout == nil {
- o.LdapSettings.QueryTimeout = new(int)
- *o.LdapSettings.QueryTimeout = 60
+ if o.LdapSettings.ConnectionSecurity == nil {
+ o.LdapSettings.ConnectionSecurity = new(string)
+ *o.LdapSettings.ConnectionSecurity = ""
}
- if o.LdapSettings.Enable == nil {
- o.LdapSettings.Enable = new(bool)
- *o.LdapSettings.Enable = false
+ if o.LdapSettings.BaseDN == nil {
+ o.LdapSettings.BaseDN = new(string)
+ *o.LdapSettings.BaseDN = ""
+ }
+
+ if o.LdapSettings.BindUsername == nil {
+ o.LdapSettings.BindUsername = new(string)
+ *o.LdapSettings.BindUsername = ""
+ }
+
+ if o.LdapSettings.BindPassword == nil {
+ o.LdapSettings.BindPassword = new(string)
+ *o.LdapSettings.BindPassword = ""
}
if o.LdapSettings.UserFilter == nil {
@@ -440,9 +470,29 @@ func (o *Config) SetDefaults() {
*o.LdapSettings.UserFilter = ""
}
- if o.LdapSettings.LoginFieldName == nil {
- o.LdapSettings.LoginFieldName = new(string)
- *o.LdapSettings.LoginFieldName = ""
+ if o.LdapSettings.FirstNameAttribute == nil {
+ o.LdapSettings.FirstNameAttribute = new(string)
+ *o.LdapSettings.FirstNameAttribute = ""
+ }
+
+ if o.LdapSettings.LastNameAttribute == nil {
+ o.LdapSettings.LastNameAttribute = new(string)
+ *o.LdapSettings.LastNameAttribute = ""
+ }
+
+ if o.LdapSettings.EmailAttribute == nil {
+ o.LdapSettings.EmailAttribute = new(string)
+ *o.LdapSettings.EmailAttribute = ""
+ }
+
+ if o.LdapSettings.NicknameAttribute == nil {
+ o.LdapSettings.NicknameAttribute = new(string)
+ *o.LdapSettings.NicknameAttribute = ""
+ }
+
+ if o.LdapSettings.IdAttribute == nil {
+ o.LdapSettings.IdAttribute = new(string)
+ *o.LdapSettings.IdAttribute = ""
}
if o.LdapSettings.SyncIntervalMinutes == nil {
@@ -450,6 +500,21 @@ func (o *Config) SetDefaults() {
*o.LdapSettings.SyncIntervalMinutes = 60
}
+ if o.LdapSettings.SkipCertificateVerification == nil {
+ o.LdapSettings.SkipCertificateVerification = new(bool)
+ *o.LdapSettings.SkipCertificateVerification = false
+ }
+
+ if o.LdapSettings.QueryTimeout == nil {
+ o.LdapSettings.QueryTimeout = new(int)
+ *o.LdapSettings.QueryTimeout = 60
+ }
+
+ if o.LdapSettings.LoginFieldName == nil {
+ o.LdapSettings.LoginFieldName = new(string)
+ *o.LdapSettings.LoginFieldName = ""
+ }
+
if o.ServiceSettings.SessionLengthWebInDays == nil {
o.ServiceSettings.SessionLengthWebInDays = new(int)
*o.ServiceSettings.SessionLengthWebInDays = 30
@@ -515,21 +580,6 @@ func (o *Config) SetDefaults() {
*o.ComplianceSettings.EnableDaily = false
}
- if o.LdapSettings.ConnectionSecurity == nil {
- o.LdapSettings.ConnectionSecurity = new(string)
- *o.LdapSettings.ConnectionSecurity = ""
- }
-
- if o.LdapSettings.SkipCertificateVerification == nil {
- o.LdapSettings.SkipCertificateVerification = new(bool)
- *o.LdapSettings.SkipCertificateVerification = false
- }
-
- if o.LdapSettings.NicknameAttribute == nil {
- o.LdapSettings.NicknameAttribute = new(string)
- *o.LdapSettings.NicknameAttribute = ""
- }
-
if o.LocalizationSettings.DefaultServerLocale == nil {
o.LocalizationSettings.DefaultServerLocale = new(string)
*o.LocalizationSettings.DefaultServerLocale = DEFAULT_LOCALE
@@ -542,7 +592,7 @@ func (o *Config) SetDefaults() {
if o.LocalizationSettings.AvailableLocales == nil {
o.LocalizationSettings.AvailableLocales = new(string)
- *o.LocalizationSettings.AvailableLocales = *o.LocalizationSettings.DefaultClientLocale
+ *o.LocalizationSettings.AvailableLocales = strings.Join(LOCALES, ",")
}
}
diff --git a/model/version.go b/model/version.go
index dde9eccd7..d486f5c57 100644
--- a/model/version.go
+++ b/model/version.go
@@ -13,6 +13,7 @@ import (
// It should be maitained in chronological order with most current
// release at the front of the list.
var versions = []string{
+ "3.1.0",
"3.0.0",
"2.2.0",
"2.1.0",
diff --git a/store/sql_team_store.go b/store/sql_team_store.go
index 6e1deeb20..c668988dc 100644
--- a/store/sql_team_store.go
+++ b/store/sql_team_store.go
@@ -10,6 +10,10 @@ import (
"github.com/mattermost/platform/utils"
)
+const (
+ TEAM_MEMBER_EXISTS_ERROR = "store.sql_team.save_member.exists.app_error"
+)
+
type SqlTeamStore struct {
*SqlStore
}
@@ -372,8 +376,8 @@ func (s SqlTeamStore) SaveMember(member *model.TeamMember) StoreChannel {
}
if err := s.GetMaster().Insert(member); err != nil {
- if IsUniqueConstraintError(err.Error(), []string{"TeamId", "teammembers_pkey"}) {
- result.Err = model.NewLocAppError("SqlTeamStore.SaveMember", "store.sql_team.save_member.exists.app_error", nil, "team_id="+member.TeamId+", user_id="+member.UserId+", "+err.Error())
+ if IsUniqueConstraintError(err.Error(), []string{"TeamId", "teammembers_pkey", "PRIMARY"}) {
+ result.Err = model.NewLocAppError("SqlTeamStore.SaveMember", TEAM_MEMBER_EXISTS_ERROR, nil, "team_id="+member.TeamId+", user_id="+member.UserId+", "+err.Error())
} else {
result.Err = model.NewLocAppError("SqlTeamStore.SaveMember", "store.sql_team.save_member.save.app_error", nil, "team_id="+member.TeamId+", user_id="+member.UserId+", "+err.Error())
}
diff --git a/webapp/actions/global_actions.jsx b/webapp/actions/global_actions.jsx
index ca3bf6362..0c07173ac 100644
--- a/webapp/actions/global_actions.jsx
+++ b/webapp/actions/global_actions.jsx
@@ -139,8 +139,8 @@ export function doFocusPost(channelId, postId, data) {
});
AsyncClient.getChannels(true);
AsyncClient.getChannelExtraInfo(channelId);
- AsyncClient.getPostsBefore(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS);
- AsyncClient.getPostsAfter(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS);
+ AsyncClient.getPostsBefore(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS, true);
+ AsyncClient.getPostsAfter(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS, true);
}
export function emitPostFocusEvent(postId) {
diff --git a/webapp/components/admin_console/admin_sidebar.jsx b/webapp/components/admin_console/admin_sidebar.jsx
index d760e3db9..c947be5cb 100644
--- a/webapp/components/admin_console/admin_sidebar.jsx
+++ b/webapp/components/admin_console/admin_sidebar.jsx
@@ -239,6 +239,22 @@ export default class AdminSidebar extends React.Component {
);
}
+ let customBranding = null;
+
+ if (window.mm_license.IsLicensed === 'true') {
+ customBranding = (
+ <AdminSidebarSection
+ name='custom_brand'
+ title={
+ <FormattedMessage
+ id='admin.sidebar.customBrand'
+ defaultMessage='Custom Branding'
+ />
+ }
+ />
+ );
+ }
+
return (
<div className='admin-sidebar'>
<AdminSidebarHeader/>
@@ -515,16 +531,7 @@ export default class AdminSidebar extends React.Component {
/>
}
>
- <AdminSidebarSection
- name='custom_brand'
- title={
- <FormattedMessage
- id='admin.sidebar.customBrand'
- defaultMessage='Custom Branding'
- />
-
- }
- />
+ {customBranding}
<AdminSidebarSection
name='legal_and_support'
title={
diff --git a/webapp/components/admin_console/ldap_settings.jsx b/webapp/components/admin_console/ldap_settings.jsx
index acda672d8..01d4b0a67 100644
--- a/webapp/components/admin_console/ldap_settings.jsx
+++ b/webapp/components/admin_console/ldap_settings.jsx
@@ -343,7 +343,7 @@ export default class LdapSettings extends AdminSettings {
helpText={
<FormattedMessage
id='admin.ldap.syncIntervalHelpText'
- defaultMessage='LDAP Synchronization is the process by which Mattermost updates its users to reflect any updated data on the LDAP server. For example if a name for a user is updated on the LDAP server, the change will be reflected in Mattermost when the synchronization is performed. Accounts that have been removed from the LDAP server will have their active sessions cleared and no longer be able to login to Mattermost. Mattermost will perform this synchronization regularly according to the interval supplied here. For example, if 60 is supplied, Mattermost will update the users every hour.'
+ defaultMessage='LDAP Synchronization is the process by which Mattermost updates its users to reflect any updated data on the LDAP server. For example if a name for a user is updated on the LDAP server, the change will be reflected in Mattermost when the synchronization is performed. Accounts that have been removed from the LDAP server will have their active sessions cleared and no longer be able to login to Mattermost. Mattermost will perform this synchronization regularly according to the interval supplied here. For example, if 60 is supplied, Mattermost will update the users every hour. Changing this will require a server restart before taking effect.'
/>
}
value={this.state.syncIntervalMinutes}
diff --git a/webapp/components/admin_console/multiselect_settings.jsx b/webapp/components/admin_console/multiselect_settings.jsx
index deba983de..ca0bdc9aa 100644
--- a/webapp/components/admin_console/multiselect_settings.jsx
+++ b/webapp/components/admin_console/multiselect_settings.jsx
@@ -50,6 +50,7 @@ export default class MultiSelectSetting extends React.Component {
labelKey='text'
options={this.props.values}
joinValues={true}
+ clearable={false}
disabled={this.props.disabled}
noResultsText={this.props.noResultText}
onChange={this.handleChange}
diff --git a/webapp/components/admin_console/storage_settings.jsx b/webapp/components/admin_console/storage_settings.jsx
index 60eedc2d2..077d75c73 100644
--- a/webapp/components/admin_console/storage_settings.jsx
+++ b/webapp/components/admin_console/storage_settings.jsx
@@ -22,8 +22,8 @@ export default class StorageSettings extends AdminSettings {
this.renderSettings = this.renderSettings.bind(this);
+ //maxFileSize: props.config.FileSettings.MaxFileSize,
this.state = Object.assign(this.state, {
- maxFileSize: props.config.FileSettings.MaxFileSize,
driverName: props.config.FileSettings.DriverName,
directory: props.config.FileSettings.Directory,
amazonS3AccessKeyId: props.config.FileSettings.AmazonS3AccessKeyId,
@@ -34,7 +34,7 @@ export default class StorageSettings extends AdminSettings {
}
getConfigFromState(config) {
- config.FileSettings.MaxFileSize = this.parseInt(this.state.maxFileSize);
+ //config.FileSettings.MaxFileSize = this.parseInt(this.state.maxFileSize);
config.FileSettings.DriverName = this.state.driverName;
config.FileSettings.Directory = this.state.directory;
config.FileSettings.AmazonS3AccessKeyId = this.state.amazonS3AccessKeyId;
@@ -57,26 +57,27 @@ export default class StorageSettings extends AdminSettings {
}
renderSettings() {
+ /*<TextSetting
+ id='maxFileSize'
+ label={
+ <FormattedMessage
+ id='admin.image.maxFileSizeTitle'
+ defaultMessage='Max File Size:'
+ />
+ }
+ placeholder={Utils.localizeMessage('admin.image.maxFileSizeExample', 'Ex "52428800"')}
+ helpText={
+ <FormattedMessage
+ id='admin.image.maxFileSizeDescription'
+ defaultMessage='Max File Size in bytes. If blank, will be set to 52428800 (50MB).'
+ />
+ }
+ value={this.state.maxFileSize}
+ onChange={this.handleChange}
+ />*/
+
return (
<SettingsGroup>
- <TextSetting
- id='maxFileSize'
- label={
- <FormattedMessage
- id='admin.image.maxFileSizeTitle'
- defaultMessage='Max File Size:'
- />
- }
- placeholder={Utils.localizeMessage('admin.image.maxFileSizeExample', 'Ex "52428800"')}
- helpText={
- <FormattedMessage
- id='admin.image.maxFileSizeDescription'
- defaultMessage='Max File Size in bytes. If blank, will be set to 52428800 (50MB).'
- />
- }
- value={this.state.maxFileSize}
- onChange={this.handleChange}
- />
<DropdownSetting
id='driverName'
values={[
diff --git a/webapp/components/analytics/doughnut_chart.jsx b/webapp/components/analytics/doughnut_chart.jsx
index b946ad8bb..5834e290a 100644
--- a/webapp/components/analytics/doughnut_chart.jsx
+++ b/webapp/components/analytics/doughnut_chart.jsx
@@ -23,26 +23,26 @@ export default class DoughnutChart extends React.Component {
componentDidUpdate(prevProps) {
if (!Utils.areObjectsEqual(prevProps.data, this.props.data) || !Utils.areObjectsEqual(prevProps.options, this.props.options)) {
- if (this.chart) {
- this.chart.destroy();
- }
- this.initChart();
+ this.initChart(true);
}
}
componentWillUnmount() {
- if (this.chart) {
+ if (this.chart && this.refs.canvas) {
this.chart.destroy();
}
}
- initChart() {
+ initChart(update) {
if (!this.refs.canvas) {
return;
}
var el = ReactDOM.findDOMNode(this.refs.canvas);
var ctx = el.getContext('2d');
this.chart = new Chart(ctx, {type: 'doughnut', data: this.props.data, options: this.props.options || {}}); //eslint-disable-line new-cap
+ if (update) {
+ this.chart.update();
+ }
}
render() {
diff --git a/webapp/components/analytics/line_chart.jsx b/webapp/components/analytics/line_chart.jsx
index bebeb0223..aa603d819 100644
--- a/webapp/components/analytics/line_chart.jsx
+++ b/webapp/components/analytics/line_chart.jsx
@@ -23,26 +23,26 @@ export default class LineChart extends React.Component {
componentDidUpdate(prevProps) {
if (!Utils.areObjectsEqual(prevProps.data, this.props.data) || !Utils.areObjectsEqual(prevProps.options, this.props.options)) {
- if (this.chart) {
- this.chart.destroy();
- }
- this.initChart();
+ this.initChart(true);
}
}
componentWillUnmount() {
- if (this.chart) {
+ if (this.chart && this.refs.canvas) {
this.chart.destroy();
}
}
- initChart() {
+ initChart(update) {
if (!this.refs.canvas) {
return;
}
var el = ReactDOM.findDOMNode(this.refs.canvas);
var ctx = el.getContext('2d');
this.chart = new Chart(ctx, {type: 'line', data: this.props.data, options: this.props.options || {}}); //eslint-disable-line new-cap
+ if (update) {
+ this.chart.update();
+ }
}
render() {
diff --git a/webapp/components/create_comment.jsx b/webapp/components/create_comment.jsx
index 454d8bf43..85f8ac864 100644
--- a/webapp/components/create_comment.jsx
+++ b/webapp/components/create_comment.jsx
@@ -147,7 +147,7 @@ class CreateComment extends React.Component {
Client.createPost(
post,
() => {
- // DO nothing. Websockets will handle this.
+ PostStore.removePendingPost(post.channel_id, post.pending_post_id);
},
(err) => {
if (err.id === 'api.post.create_post.root_id.app_error') {
diff --git a/webapp/components/create_post.jsx b/webapp/components/create_post.jsx
index 7aa0a586b..caf58c744 100644
--- a/webapp/components/create_post.jsx
+++ b/webapp/components/create_post.jsx
@@ -180,7 +180,7 @@ class CreatePost extends React.Component {
Client.createPost(post,
() => {
- // DO nothing. Websockets will handle this.
+ PostStore.removePendingPost(post.pending_post_id);
},
(err) => {
if (err.id === 'api.post.create_post.root_id.app_error') {
diff --git a/webapp/components/edit_post_modal.jsx b/webapp/components/edit_post_modal.jsx
index bdd540b09..4bd23a26d 100644
--- a/webapp/components/edit_post_modal.jsx
+++ b/webapp/components/edit_post_modal.jsx
@@ -67,6 +67,7 @@ class EditPostModal extends React.Component {
Client.updatePost(
updatedPost,
() => {
+ AsyncClient.getPosts(updatedPost.channel_id);
window.scrollTo(0, 0);
},
(err) => {
diff --git a/webapp/components/login/login_controller.jsx b/webapp/components/login/login_controller.jsx
index dd4a92f92..ab8b49392 100644
--- a/webapp/components/login/login_controller.jsx
+++ b/webapp/components/login/login_controller.jsx
@@ -69,13 +69,13 @@ export default class LoginController extends React.Component {
this.setState({loginId});
}
- let password = this.refs.password.value;
+ const password = this.refs.password.value;
if (password !== this.state.password) {
this.setState({password});
}
+ // don't trim the password since we support spaces in passwords
loginId = loginId.trim();
- password = password.trim();
if (!loginId) {
// it's slightly weird to be constructing the message ID, but it's a bit nicer than triply nested if statements
@@ -444,7 +444,7 @@ export default class LoginController extends React.Component {
<a
className='btn btn-custom-login gitlab'
key='gitlab'
- href={Client.getOAuthRoute() + '/gitlab/login'}
+ href={Client.getOAuthRoute() + '/gitlab/login' + this.props.location.search}
>
<span className='icon'/>
<span>
@@ -529,4 +529,4 @@ export default class LoginController extends React.Component {
</div>
);
}
-} \ No newline at end of file
+}
diff --git a/webapp/components/rhs_root_post.jsx b/webapp/components/rhs_root_post.jsx
index 8996381ba..ff6452035 100644
--- a/webapp/components/rhs_root_post.jsx
+++ b/webapp/components/rhs_root_post.jsx
@@ -175,6 +175,7 @@ export default class RhsRootPost extends React.Component {
filenames={post.filenames}
channelId={post.channel_id}
userId={post.user_id}
+ compactDisplay={this.props.compactDisplay}
/>
);
}
diff --git a/webapp/components/signup_user_complete.jsx b/webapp/components/signup_user_complete.jsx
index b3825f72f..a9d96e320 100644
--- a/webapp/components/signup_user_complete.jsx
+++ b/webapp/components/signup_user_complete.jsx
@@ -173,11 +173,24 @@ export default class SignupUserComplete extends React.Component {
this.state.ldapPassword,
null,
() => {
- GlobalActions.emitInitialLoad(
- () => {
- browserHistory.push('/select_team');
- }
- );
+ if (this.props.location.query.id || this.props.location.query.h) {
+ Client.addUserToTeamFromInvite(
+ this.props.location.query.d,
+ this.props.location.query.h,
+ this.props.location.query.id,
+ () => {
+ this.finishSignup();
+ },
+ () => {
+ // there's not really a good way to deal with this, so just let the user log in like normal
+ this.finishSignup();
+ }
+ );
+
+ return;
+ }
+
+ this.finishSignup();
},
(err) => {
if (err.id === 'ent.ldap.do_login.user_not_registered.app_error' || err.id === 'ent.ldap.do_login.user_filtered.app_error') {
@@ -205,6 +218,15 @@ export default class SignupUserComplete extends React.Component {
);
}
+ finishSignup() {
+ GlobalActions.emitInitialLoad(
+ () => {
+ GlobalActions.loadDefaultLocale();
+ browserHistory.push('/select_team');
+ }
+ );
+ }
+
handleUserCreated(user, data) {
track('signup', 'signup_user_02_complete');
Client.loginById(
@@ -761,4 +783,4 @@ export default class SignupUserComplete extends React.Component {
</div>
);
}
-} \ No newline at end of file
+}
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index 1e0788ebe..85f221c7c 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -181,6 +181,7 @@
"admin.email.smtpUsernameTitle": "SMTP Username:",
"admin.email.testing": "Testing...",
"admin.false": "false",
+ "admin.general.localization": "Localization",
"admin.general.localization.availableLocalesDescription": "Determines which languages are available for users in Account Settings.",
"admin.general.localization.clientLocaleDescription": "Default language for newly created users and pages where the user hasn't logged in.",
"admin.general.localization.serverLocaleDescription": "Default language for system messages and logs. Changing this will require a server restart before taking effect.",
@@ -283,7 +284,7 @@
"admin.ldap.serverTitle": "LDAP Server:",
"admin.ldap.skipCertificateVerification": "Skip Certificate Verification",
"admin.ldap.skipCertificateVerificationDesc": "Skips the certificate verification step for TLS or STARTTLS connections. Not recommended for production environments where TLS is required. For testing only.",
- "admin.ldap.syncIntervalHelpText": "LDAP Synchronization is the process by which Mattermost updates its users to reflect any updated data on the LDAP server. For example if a name for a user is updated on the LDAP server, the change will be reflected in Mattermost when the synchronization is performed. Accounts that have been removed from the LDAP server will have their active sessions cleared and no longer be able to login to Mattermost. Mattermost will perform this synchronization regularly according to the interval supplied here. For example, if 60 is supplied, Mattermost will update the users every hour.",
+ "admin.ldap.syncIntervalHelpText": "LDAP Synchronization is the process by which Mattermost updates its users to reflect any updated data on the LDAP server. For example if a name for a user is updated on the LDAP server, the change will be reflected in Mattermost when the synchronization is performed. Accounts that have been removed from the LDAP server will have their active sessions cleared and no longer be able to login to Mattermost. Mattermost will perform this synchronization regularly according to the interval supplied here. For example, if 60 is supplied, Mattermost will update the users every hour. Changing this will require a server restart before taking effect.",
"admin.ldap.syncIntervalTitle": "Synchronization Interval (In Minutes)",
"admin.ldap.uernameAttrDesc": "The attribute in the LDAP server that will be used to populate the username field in Mattermost. This may be the same as the ID Attribute.",
"admin.ldap.userFilterDisc": "Optionally enter an LDAP Filter to use when searching for user objects. Only the users selected by the query will be able to access Mattermost. For Active Directory, the query to filter out disabled users is (&(objectCategory=Person)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))).",
@@ -433,6 +434,7 @@
"admin.sidebar.integrations": "Integrations",
"admin.sidebar.ldap": "LDAP",
"admin.sidebar.license": "Edition and License",
+ "admin.sidebar.localization": "Localization",
"admin.sidebar.logging": "Logging",
"admin.sidebar.login": "Login",
"admin.sidebar.logs": "Logs",
diff --git a/webapp/i18n/es.json b/webapp/i18n/es.json
index 5680d7cb9..6ce2e77c3 100644
--- a/webapp/i18n/es.json
+++ b/webapp/i18n/es.json
@@ -181,6 +181,7 @@
"admin.email.smtpUsernameTitle": "Usuario SMTP:",
"admin.email.testing": "Probando...",
"admin.false": "falso",
+ "admin.general.localization": "Idiomas",
"admin.general.localization.availableLocalesDescription": "Determina qué idiomas están disponibles para los usuarios en la Configuración de la Cuenta.",
"admin.general.localization.clientLocaleDescription": "Idioma predeterminado para nuevos usuarios y páginas donde el usuario no ha iniciado sesión.",
"admin.general.localization.serverLocaleDescription": "Idioma predeterminado para los mensajes del sistema y los registros. Cambiar esto requerirá un reinicio del servidor antes de tomar efecto.",
@@ -431,6 +432,7 @@
"admin.sidebar.integrations": "Integraciones",
"admin.sidebar.ldap": "LDAP",
"admin.sidebar.license": "Edición y Licencia",
+ "admin.sidebar.localization": "Idiomas",
"admin.sidebar.logging": "Registros",
"admin.sidebar.login": "Inicio de Sesión",
"admin.sidebar.logs": "Registros",
diff --git a/webapp/i18n/i18n.jsx b/webapp/i18n/i18n.jsx
index 783cef975..3a66c5c33 100644
--- a/webapp/i18n/i18n.jsx
+++ b/webapp/i18n/i18n.jsx
@@ -13,6 +13,7 @@ import frLocaleData from 'react-intl/locale-data/fr';
import jaLocaleData from 'react-intl/locale-data/ja';
import ptLocaleData from 'react-intl/locale-data/pt';
+// should match the values in model/config.go
const languages = {
en: {
value: 'en',
diff --git a/webapp/package.json b/webapp/package.json
index 7535f5e04..5bc944557 100644
--- a/webapp/package.json
+++ b/webapp/package.json
@@ -18,7 +18,7 @@
"keymirror": "0.1.1",
"marked": "mattermost/marked#2d92082cd542dc0adc175a1407194f6a4c3cb621",
"match-at": "0.1.0",
- "mattermost": "mattermost/mattermost-javascript#master",
+ "mattermost": "mattermost/mattermost-javascript#release-3.1",
"object-assign": "4.1.0",
"perfect-scrollbar": "0.6.11",
"react": "15.0.2",
diff --git a/webapp/sass/components/_mentions.scss b/webapp/sass/components/_mentions.scss
index 9e47e2a71..82f626eaf 100644
--- a/webapp/sass/components/_mentions.scss
+++ b/webapp/sass/components/_mentions.scss
@@ -13,10 +13,10 @@
@include clearfix;
cursor: pointer;
font-size: 13px;
- height: 36px;
- line-height: 36px;
- margin: 5px 0;
- padding: 2px;
+ height: 39px;
+ line-height: 35px;
+ margin: 0;
+ padding: 3px 8px;
position: relative;
white-space: nowrap;
width: 100%;
diff --git a/webapp/stores/post_store.jsx b/webapp/stores/post_store.jsx
index 73cb60314..b77c7bd3c 100644
--- a/webapp/stores/post_store.jsx
+++ b/webapp/stores/post_store.jsx
@@ -552,7 +552,7 @@ PostStore.dispatchToken = AppDispatcher.register((payload) => {
switch (action.type) {
case ActionTypes.RECEIVED_POSTS: {
- const id = PostStore.currentFocusedPostId == null ? action.id : PostStore.currentFocusedPostId;
+ const id = PostStore.currentFocusedPostId !== null && action.isPost ? PostStore.currentFocusedPostId : action.id;
PostStore.storePosts(id, makePostListNonNull(action.post_list));
PostStore.checkBounds(id, action.numRequested, makePostListNonNull(action.post_list), action.before);
PostStore.emitChange();
diff --git a/webapp/stores/user_store.jsx b/webapp/stores/user_store.jsx
index f57ecf1cd..218a7f1db 100644
--- a/webapp/stores/user_store.jsx
+++ b/webapp/stores/user_store.jsx
@@ -104,7 +104,7 @@ class UserStoreClass extends EventEmitter {
this.currentUserId = user.id;
global.window.mm_current_user_id = this.currentUserId;
if (LocalizationStore.getLocale() !== user.locale) {
- GlobalActions.newLocalizationSelected(user.locale);
+ setTimeout(() => GlobalActions.newLocalizationSelected(user.locale), 0);
}
}
diff --git a/webapp/utils/async_client.jsx b/webapp/utils/async_client.jsx
index 6f5f8a2cd..65cb2d258 100644
--- a/webapp/utils/async_client.jsx
+++ b/webapp/utils/async_client.jsx
@@ -603,7 +603,7 @@ export function getPosts(id) {
);
}
-export function getPostsBefore(postId, offset, numPost) {
+export function getPostsBefore(postId, offset, numPost, isPost) {
const channelId = ChannelStore.getCurrentId();
if (channelId == null) {
return;
@@ -624,7 +624,8 @@ export function getPostsBefore(postId, offset, numPost) {
id: channelId,
before: true,
numRequested: numPost,
- post_list: data
+ post_list: data,
+ isPost
});
getProfiles();
@@ -638,7 +639,7 @@ export function getPostsBefore(postId, offset, numPost) {
);
}
-export function getPostsAfter(postId, offset, numPost) {
+export function getPostsAfter(postId, offset, numPost, isPost) {
const channelId = ChannelStore.getCurrentId();
if (channelId == null) {
return;
@@ -659,7 +660,8 @@ export function getPostsAfter(postId, offset, numPost) {
id: channelId,
before: false,
numRequested: numPost,
- post_list: data
+ post_list: data,
+ isPost
});
getProfiles();
diff --git a/webapp/utils/markdown.jsx b/webapp/utils/markdown.jsx
index 73af4ae91..bd1e998b4 100644
--- a/webapp/utils/markdown.jsx
+++ b/webapp/utils/markdown.jsx
@@ -163,7 +163,7 @@ class MattermostMarkdownRenderer extends marked.Renderer {
}
// remove any links added to the text by hashtag or mention parsing since they'll break this link
- output += '>' + text.replace(/<\/?a[^>]*>/, '') + '</a>';
+ output += '>' + text.replace(/<\/?a[^>]*>/g, '') + '</a>';
return output;
}