diff options
31 files changed, 140 insertions, 122 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 142e15d91..a21f118b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,7 +108,7 @@ The following is for informational purposes only, no action needed. Mattermost a - Editing a post so that it's text is blank (which should delete it) throws a 404 - No scroll bar in centre channel - Theme color import from Slack fails to import the “Active Channel” selection color -- Pasting images into text box fails to upload on [BROWSERS] +- Pasting images into text box fails to upload on Firefox and Safari - Users cannot claim accounts imported from Slack via password reset - Slack import @mentions break diff --git a/api/user.go b/api/user.go index 4a52cf88b..0f868a678 100644 --- a/api/user.go +++ b/api/user.go @@ -669,6 +669,7 @@ func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) { } p.Sanitize(options) + p.ClearNonProfileFields() profiles[k] = p } diff --git a/doc/install/Production-Ubuntu.md b/doc/install/Production-Ubuntu.md index e792a551c..098707ada 100644 --- a/doc/install/Production-Ubuntu.md +++ b/doc/install/Production-Ubuntu.md @@ -24,6 +24,16 @@ * ```postgre=# \q``` 1. You can exit the postgres account by typing: * ``` exit``` +1. Allow Postgres to listen on all assigned IP Addresses + * ```sudo vi /etc/postgresql/9.3/main/postgresql.conf``` + * Uncomment 'listen_addresses' and change 'localhost' to '*' +1. Alter pg_hba.conf to allow the mattermost server to talk to the postgres database + * ```sudo vi /etc/postgresql/9.3/main/pg_hba.conf``` + * Add the following line to the 'IPv4 local connections' + * host all all 10.10.10.2/32 md5 +1. Reload Postgres database + * ```sudo /etc/init.d/postgresql reload``` + ## Set up Mattermost Server 1. For the purposes of this guide we will assume this server has an IP address of 10.10.10.2 diff --git a/doc/process/documentation-guidelines.md b/doc/process/documentation-guidelines.md index f37f0c5fc..59ed0a445 100644 --- a/doc/process/documentation-guidelines.md +++ b/doc/process/documentation-guidelines.md @@ -49,7 +49,7 @@ Headings in markdown provide anchors that can be used to easily reference sub-se ##### Step 1: Add a heading This makes things easier to reference via hyperlinks ##### Step 2: Link to headings -So things are eariser to find +So things are easier to find ---- ##### Incorrect: @@ -58,7 +58,7 @@ So things are eariser to find **Step 1: Add a heading** This makes things easier to reference via hyperlinks **Step 2: Link to headings** -So things are eariser to find +So things are easier to find ---- ### Use appropriate heading case diff --git a/docker/1.0/Dockerrun.aws.zip b/docker/1.0/Dockerrun.aws.zip Binary files differdeleted file mode 100644 index 8c2c16e10..000000000 --- a/docker/1.0/Dockerrun.aws.zip +++ /dev/null diff --git a/docker/1.0/Dockerfile b/docker/1.2/Dockerfile index a41c73514..6a8d69196 100644 --- a/docker/1.0/Dockerfile +++ b/docker/1.2/Dockerfile @@ -34,7 +34,7 @@ VOLUME /var/lib/mysql WORKDIR /mattermost # Copy over files -ADD https://github.com/mattermost/platform/releases/download/v1.0.0/mattermost.tar.gz / +ADD https://github.com/mattermost/platform/releases/download/v1.2.0-rc1/mattermost.tar.gz / RUN tar -zxvf /mattermost.tar.gz --strip-components=1 && rm /mattermost.tar.gz ADD config_docker.json / ADD docker-entry.sh / diff --git a/docker/1.2/Dockerrun.aws.zip b/docker/1.2/Dockerrun.aws.zip Binary files differnew file mode 100644 index 000000000..4de6ec362 --- /dev/null +++ b/docker/1.2/Dockerrun.aws.zip diff --git a/docker/1.0/Dockerrun.aws/.ebextensions/01_files.config b/docker/1.2/Dockerrun.aws/.ebextensions/01_files.config index 7f40a8b34..7f40a8b34 100644 --- a/docker/1.0/Dockerrun.aws/.ebextensions/01_files.config +++ b/docker/1.2/Dockerrun.aws/.ebextensions/01_files.config diff --git a/docker/1.0/Dockerrun.aws/Dockerrun.aws.json b/docker/1.2/Dockerrun.aws/Dockerrun.aws.json index 9fdded15e..c32a998e4 100755 --- a/docker/1.0/Dockerrun.aws/Dockerrun.aws.json +++ b/docker/1.2/Dockerrun.aws/Dockerrun.aws.json @@ -1,7 +1,7 @@ {
"AWSEBDockerrunVersion": "1",
"Image": {
- "Name": "mattermost/platform:1.0",
+ "Name": "mattermost/platform:1.2",
"Update": "true"
},
"Ports": [
diff --git a/docker/1.2/README.md b/docker/1.2/README.md new file mode 100644 index 000000000..f737a1554 --- /dev/null +++ b/docker/1.2/README.md @@ -0,0 +1,23 @@ +Mattermost +========== + +http:/mattermost.org + +Mattermost is an open-source team communication service. It brings team messaging and file sharing into one place, accessible across PCs and phones, with archiving and search. + +Installing Mattermost +===================== + +To run an instance of the latest version of mattermost on your local machine you can run: + +`docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform` + +To update this image to the latest version you can run: + +`docker pull mattermost/platform` + +To run an instance of the latest code from the master branch on GitHub you can run: + +`docker run --name mattermost-dev -d --publish 8065:80 mattermost/platform:dev` + +Any questions, please visit http://forum.mattermost.org diff --git a/docker/1.0/config_docker.json b/docker/1.2/config_docker.json index 806071376..80e6ab14e 100644 --- a/docker/1.0/config_docker.json +++ b/docker/1.2/config_docker.json @@ -5,15 +5,21 @@ "SegmentDeveloperKey": "", "GoogleDeveloperKey": "", "EnableOAuthServiceProvider": false, - "EnableIncomingWebhooks": false, - "EnableTesting": false + "EnableIncomingWebhooks": true, + "EnableOutgoingWebhooks": true, + "EnablePostUsernameOverride": false, + "EnablePostIconOverride": false, + "EnableTesting": false, + "EnableSecurityFixAlert": true }, "TeamSettings": { "SiteName": "Mattermost", "MaxUsersPerTeam": 50, "EnableTeamCreation": true, "EnableUserCreation": true, - "RestrictCreationToDomains": "" + "RestrictCreationToDomains": "", + "RestrictTeamNames": true, + "EnableTeamListing": false }, "SqlSettings": { "DriverName": "mysql", @@ -86,4 +92,4 @@ "TokenEndpoint": "", "UserApiEndpoint": "" } -}
\ No newline at end of file +} diff --git a/docker/1.0/docker-entry.sh b/docker/1.2/docker-entry.sh index 6bd2a1263..6bd2a1263 100755 --- a/docker/1.0/docker-entry.sh +++ b/docker/1.2/docker-entry.sh diff --git a/model/post.go b/model/post.go index 248d40321..5578514b5 100644 --- a/model/post.go +++ b/model/post.go @@ -26,7 +26,6 @@ type Post struct { ParentId string `json:"parent_id"` OriginalId string `json:"original_id"` Message string `json:"message"` - ImgCount int64 `json:"img_count"` Type string `json:"type"` Props StringInterface `json:"props"` Hashtags string `json:"hashtags"` diff --git a/model/user.go b/model/user.go index 4365f47d2..0ce8e1733 100644 --- a/model/user.go +++ b/model/user.go @@ -28,29 +28,29 @@ const ( type User struct { Id string `json:"id"` - CreateAt int64 `json:"create_at"` - UpdateAt int64 `json:"update_at"` + CreateAt int64 `json:"create_at,omitempty"` + UpdateAt int64 `json:"update_at,omitempty"` DeleteAt int64 `json:"delete_at"` TeamId string `json:"team_id"` Username string `json:"username"` - Password string `json:"password"` - AuthData string `json:"auth_data"` - AuthService string `json:"auth_service"` + Password string `json:"password,omitempty"` + AuthData string `json:"auth_data,omitempty"` + AuthService string `json:"auth_service,omitempty"` Email string `json:"email"` - EmailVerified bool `json:"email_verified"` + EmailVerified bool `json:"email_verified,omitempty"` Nickname string `json:"nickname"` FirstName string `json:"first_name"` LastName string `json:"last_name"` Roles string `json:"roles"` - LastActivityAt int64 `json:"last_activity_at"` - LastPingAt int64 `json:"last_ping_at"` - AllowMarketing bool `json:"allow_marketing"` - Props StringMap `json:"props"` - NotifyProps StringMap `json:"notify_props"` - ThemeProps StringMap `json:"theme_props"` - LastPasswordUpdate int64 `json:"last_password_update"` - LastPictureUpdate int64 `json:"last_picture_update"` - FailedAttempts int `json:"failed_attempts"` + LastActivityAt int64 `json:"last_activity_at,omitempty"` + LastPingAt int64 `json:"last_ping_at,omitempty"` + AllowMarketing bool `json:"allow_marketing,omitempty"` + Props StringMap `json:"props,omitempty"` + NotifyProps StringMap `json:"notify_props,omitempty"` + ThemeProps StringMap `json:"theme_props,omitempty"` + LastPasswordUpdate int64 `json:"last_password_update,omitempty"` + LastPictureUpdate int64 `json:"last_picture_update,omitempty"` + FailedAttempts int `json:"failed_attempts,omitempty"` } // IsValid validates the user and returns an error if it isn't configured @@ -221,17 +221,29 @@ func (u *User) Sanitize(options map[string]bool) { u.FirstName = "" u.LastName = "" } - if len(options) != 0 && !options["skypeid"] { - // TODO - fill in when SkypeId is added to user model - } - if len(options) != 0 && !options["phonenumber"] { - // TODO - fill in when PhoneNumber is added to user model - } if len(options) != 0 && !options["passwordupdate"] { u.LastPasswordUpdate = 0 } } +func (u *User) ClearNonProfileFields() { + u.CreateAt = 0 + u.UpdateAt = 0 + u.Password = "" + u.AuthData = "" + u.AuthService = "" + u.EmailVerified = false + u.LastActivityAt = 0 + u.LastPingAt = 0 + u.AllowMarketing = false + u.Props = StringMap{} + u.NotifyProps = StringMap{} + u.ThemeProps = StringMap{} + u.LastPasswordUpdate = 0 + u.LastPictureUpdate = 0 + u.FailedAttempts = 0 +} + func (u *User) MakeNonNil() { if u.Props == nil { u.Props = make(map[string]string) diff --git a/model/version.go b/model/version.go index a61004fde..6eae436bc 100644 --- a/model/version.go +++ b/model/version.go @@ -12,6 +12,7 @@ import ( // It should be maitained in chronological order with most current // release at the front of the list. var versions = []string{ + "1.2.0", "1.1.0", "1.0.0", "0.7.1", diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go index 2d62b8614..fc4e19442 100644 --- a/store/sql_channel_store.go +++ b/store/sql_channel_store.go @@ -4,7 +4,6 @@ package store import ( - l4g "code.google.com/p/log4go" "github.com/go-gorp/gorp" "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" @@ -40,55 +39,6 @@ func NewSqlChannelStore(sqlStore *SqlStore) ChannelStore { } func (s SqlChannelStore) UpgradeSchemaIfNeeded() { - - // REMOVE AFTER 1.2 SHIP see PLT-828 - if s.CreateColumnIfNotExists("ChannelMembers", "NotifyProps", "varchar(2000)", "varchar(2000)", "{}") { - // populate NotifyProps from existing NotifyLevel field - - // set default values - _, err := s.GetMaster().Exec( - `UPDATE - ChannelMembers - SET - NotifyProps = CONCAT('{"desktop":"', CONCAT(NotifyLevel, '","mark_unread":"` + model.CHANNEL_MARK_UNREAD_ALL + `"}'))`) - if err != nil { - l4g.Error("Unable to set default values for ChannelMembers.NotifyProps") - l4g.Error(err.Error()) - } - - // assume channels with all notifications enabled are just using the default settings - _, err = s.GetMaster().Exec( - `UPDATE - ChannelMembers - SET - NotifyProps = '{"desktop":"` + model.CHANNEL_NOTIFY_DEFAULT + `","mark_unread":"` + model.CHANNEL_MARK_UNREAD_ALL + `"}' - WHERE - NotifyLevel = '` + model.CHANNEL_NOTIFY_ALL + `'`) - if err != nil { - l4g.Error("Unable to set values for ChannelMembers.NotifyProps when members previously had notifyLevel=all") - l4g.Error(err.Error()) - } - - // set quiet mode channels to have no notifications and only mark the channel unread on mentions - _, err = s.GetMaster().Exec( - `UPDATE - ChannelMembers - SET - NotifyProps = '{"desktop":"` + model.CHANNEL_NOTIFY_NONE + `","mark_unread":"` + model.CHANNEL_MARK_UNREAD_MENTION + `"}' - WHERE - NotifyLevel = 'quiet'`) - if err != nil { - l4g.Error("Unable to set values for ChannelMembers.NotifyProps when members previously had notifyLevel=quiet") - l4g.Error(err.Error()) - } - - s.RemoveColumnIfExists("ChannelMembers", "NotifyLevel") - } - - // BEGIN REMOVE AFTER 1.2.0 - s.RenameColumnIfExists("Channels", "Description", "Header", "varchar(1024)") - s.CreateColumnIfNotExists("Channels", "Purpose", "varchar(1024)", "varchar(1024)", "") - // END REMOVE AFTER 1.2.0 } func (s SqlChannelStore) CreateIndexesIfNotExists() { diff --git a/store/sql_post_store.go b/store/sql_post_store.go index 3460fca92..f800367cb 100644 --- a/store/sql_post_store.go +++ b/store/sql_post_store.go @@ -9,10 +9,8 @@ import ( "strconv" "strings" - l4g "code.google.com/p/log4go" "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" - "time" ) type SqlPostStore struct { @@ -40,21 +38,7 @@ func NewSqlPostStore(sqlStore *SqlStore) PostStore { } func (s SqlPostStore) UpgradeSchemaIfNeeded() { - colType := s.GetColumnDataType("Posts", "Props") - if colType != "text" { - - query := "ALTER TABLE Posts MODIFY COLUMN Props TEXT" - if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES { - query = "ALTER TABLE Posts ALTER COLUMN Props TYPE text" - } - - _, err := s.GetMaster().Exec(query) - if err != nil { - l4g.Critical("Failed to alter column Posts.Props to TEXT: " + err.Error()) - time.Sleep(time.Second) - panic("Failed to alter column Posts.Props to TEXT: " + err.Error()) - } - } + s.RemoveColumnIfExists("Posts", "ImgCount") // remove after 1.3 release } func (s SqlPostStore) CreateIndexesIfNotExists() { diff --git a/store/sql_team_store.go b/store/sql_team_store.go index e0f95fa7e..1a0aeabde 100644 --- a/store/sql_team_store.go +++ b/store/sql_team_store.go @@ -30,11 +30,6 @@ func NewSqlTeamStore(sqlStore *SqlStore) TeamStore { } func (s SqlTeamStore) UpgradeSchemaIfNeeded() { - // REMOVE AFTER 1.2 SHIP see PLT-828 - s.RemoveColumnIfExists("Teams", "AllowValet") - s.CreateColumnIfNotExists("Teams", "InviteId", "varchar(32)", "varchar(32)", "") - s.CreateColumnIfNotExists("Teams", "AllowOpenInvite", "tinyint(1)", "boolean", "0") - s.CreateColumnIfNotExists("Teams", "AllowTeamListing", "tinyint(1)", "boolean", "0") } func (s SqlTeamStore) CreateIndexesIfNotExists() { diff --git a/store/sql_user_store.go b/store/sql_user_store.go index 686949a4d..d38b5c214 100644 --- a/store/sql_user_store.go +++ b/store/sql_user_store.go @@ -41,8 +41,6 @@ func NewSqlUserStore(sqlStore *SqlStore) UserStore { } func (us SqlUserStore) UpgradeSchemaIfNeeded() { - // REMOVE AFTER 1.2 SHIP see PLT-828 - us.CreateColumnIfNotExists("Users", "ThemeProps", "varchar(2000)", "character varying(2000)", "{}") } func (us SqlUserStore) CreateIndexesIfNotExists() { diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx index 40e00ff04..0cabf7f70 100644 --- a/web/react/components/admin_console/email_settings.jsx +++ b/web/react/components/admin_console/email_settings.jsx @@ -296,7 +296,7 @@ export default class EmailSettings extends React.Component { className='form-control' id='feedbackName' ref='feedbackName' - placeholder='Ex: "Mattermost Notification", "System", "No-Reply"' + placeholder='E.g.: "Mattermost Notification", "System", "No-Reply"' defaultValue={this.props.config.EmailSettings.FeedbackName} onChange={this.handleChange} disabled={!this.state.sendEmailNotifications} @@ -318,7 +318,7 @@ export default class EmailSettings extends React.Component { className='form-control' id='feedbackEmail' ref='feedbackEmail' - placeholder='Ex: "mattermost@yourcompany.com", "admin@yourcompany.com"' + placeholder='E.g.: "mattermost@yourcompany.com", "admin@yourcompany.com"' defaultValue={this.props.config.EmailSettings.FeedbackEmail} onChange={this.handleChange} disabled={!this.state.sendEmailNotifications} @@ -340,7 +340,7 @@ export default class EmailSettings extends React.Component { className='form-control' id='SMTPUsername' ref='SMTPUsername' - placeholder='Ex: "admin@yourcompany.com", "AKIADTOVBGERKLCBV"' + placeholder='E.g.: "admin@yourcompany.com", "AKIADTOVBGERKLCBV"' defaultValue={this.props.config.EmailSettings.SMTPUsername} onChange={this.handleChange} disabled={!this.state.sendEmailNotifications} @@ -362,7 +362,7 @@ export default class EmailSettings extends React.Component { className='form-control' id='SMTPPassword' ref='SMTPPassword' - placeholder='Ex: "yourpassword", "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"' + placeholder='E.g.: "yourpassword", "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"' defaultValue={this.props.config.EmailSettings.SMTPPassword} onChange={this.handleChange} disabled={!this.state.sendEmailNotifications} @@ -384,7 +384,7 @@ export default class EmailSettings extends React.Component { className='form-control' id='SMTPServer' ref='SMTPServer' - placeholder='Ex: "smtp.yourcompany.com", "email-smtp.us-east-1.amazonaws.com"' + placeholder='E.g.: "smtp.yourcompany.com", "email-smtp.us-east-1.amazonaws.com"' defaultValue={this.props.config.EmailSettings.SMTPServer} onChange={this.handleChange} disabled={!this.state.sendEmailNotifications} @@ -406,7 +406,7 @@ export default class EmailSettings extends React.Component { className='form-control' id='SMTPPort' ref='SMTPPort' - placeholder='Ex: "25", "465"' + placeholder='E.g.: "25", "465"' defaultValue={this.props.config.EmailSettings.SMTPPort} onChange={this.handleChange} disabled={!this.state.sendEmailNotifications} @@ -476,7 +476,7 @@ export default class EmailSettings extends React.Component { className='form-control' id='InviteSalt' ref='InviteSalt' - placeholder='Ex "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"' + placeholder='E.g.: "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"' defaultValue={this.props.config.EmailSettings.InviteSalt} onChange={this.handleChange} disabled={!this.state.sendEmailNotifications} @@ -507,7 +507,7 @@ export default class EmailSettings extends React.Component { className='form-control' id='PasswordResetSalt' ref='PasswordResetSalt' - placeholder='Ex "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"' + placeholder='E.g.: "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"' defaultValue={this.props.config.EmailSettings.PasswordResetSalt} onChange={this.handleChange} disabled={!this.state.sendEmailNotifications} diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx index 2b9ce67ca..f7f5bd23d 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -215,11 +215,11 @@ export default class Login extends React.Component { return ( <div className='signup-team__container'> + {verifiedBox} <h5 className='margin--less'>{'Sign in to:'}</h5> <h2 className='signup-team__name'>{teamDisplayName}</h2> <h2 className='signup-team__subdomain'>{'on '}{global.window.mm_config.SiteName}</h2> <form onSubmit={this.handleSubmit}> - {verifiedBox} <div className={'form-group' + errorClass}> {serverError} </div> diff --git a/web/react/components/new_channel_modal.jsx b/web/react/components/new_channel_modal.jsx index c0cea496f..2c044cd5d 100644 --- a/web/react/components/new_channel_modal.jsx +++ b/web/react/components/new_channel_modal.jsx @@ -115,7 +115,7 @@ export default class NewChannelModal extends React.Component { type='text' ref='display_name' className='form-control' - placeholder='Ex: "Bugs", "Marketing", "办公室恋情"' + placeholder='E.g.: "Bugs", "Marketing", "办公室恋情"' maxLength='22' value={this.props.channelData.displayName} autoFocus={true} diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx index 2b81d1d79..b782268fa 100644 --- a/web/react/components/posts_view.jsx +++ b/web/react/components/posts_view.jsx @@ -30,6 +30,9 @@ export default class PostsView extends React.Component { static get SIDEBAR_OPEN() { return 3; } + static get SCROLL_TYPE_NEW_MESSAGE() { + return 4; + } isAtBottom() { return ((this.refs.postlist.scrollHeight - this.refs.postlist.scrollTop) === this.refs.postlist.clientHeight); } @@ -145,6 +148,7 @@ export default class PostsView extends React.Component { <div id={newSeparatorId} key='unviewed' + ref='newMessageSeparator' className='new-separator' > <hr @@ -165,6 +169,15 @@ export default class PostsView extends React.Component { window.requestAnimationFrame(() => { this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight; }); + } else if (this.props.scrollType === PostsView.SCROLL_TYPE_NEW_MESSAGE) { + window.requestAnimationFrame(() => { + // If separator exists scroll to it. Otherwise scroll to bottom. + if (this.refs.newMessageSeparator) { + this.refs.newMessageSeparator.scrollIntoView(); + } else { + this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight; + } + }); } else if (this.props.scrollType === PostsView.SCROLL_TYPE_POST && this.props.scrollPost) { window.requestAnimationFrame(() => { const postNode = ReactDOM.findDOMNode(this.refs[this.props.scrollPost]); diff --git a/web/react/components/posts_view_container.jsx b/web/react/components/posts_view_container.jsx index 5059747bd..5037a86cd 100644 --- a/web/react/components/posts_view_container.jsx +++ b/web/react/components/posts_view_container.jsx @@ -109,7 +109,7 @@ export default class PostsViewContainer extends React.Component { this.setState({ currentChannelIndex: newIndex, currentLastViewed: lastViewed, - scrollType: PostsView.SCROLL_TYPE_BOTTOM, + scrollType: PostsView.SCROLL_TYPE_NEW_MESSAGE, channels, postLists}); } diff --git a/web/react/components/tutorial/tutorial_intro_screens.jsx b/web/react/components/tutorial/tutorial_intro_screens.jsx index 66ca556c6..3afc5145d 100644 --- a/web/react/components/tutorial/tutorial_intro_screens.jsx +++ b/web/react/components/tutorial/tutorial_intro_screens.jsx @@ -41,6 +41,11 @@ export default class TutorialIntroScreens extends React.Component { componentDidMount() { $('.tutorials__scroll').perfectScrollbar(); } + skipTutorial(e) { + e.preventDefault(); + const preference = PreferenceStore.setPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), '999'); + AsyncClient.savePreferences([preference]); + } createScreen() { switch (this.state.currentScreen) { case 0: @@ -176,6 +181,13 @@ export default class TutorialIntroScreens extends React.Component { > {'Next'} </button> + <a + className='tutorial-skip' + href='#' + onClick={this.skipTutorial} + > + {'Skip tutorial'} + </a> </div> </div> </div> diff --git a/web/react/components/tutorial/tutorial_tip.jsx b/web/react/components/tutorial/tutorial_tip.jsx index 3094b2f4c..75d73e920 100644 --- a/web/react/components/tutorial/tutorial_tip.jsx +++ b/web/react/components/tutorial/tutorial_tip.jsx @@ -69,11 +69,16 @@ export default class TutorialTip extends React.Component { } } + var tipColor = ''; + if (this.props.overlayClass === 'tip-overlay--header') { + tipColor = 'White'; + } + return ( <div className={'tip-div ' + this.props.overlayClass}> <img className='tip-button' - src='/static/images/tutorialTip.gif' + src={'/static/images/tutorialTip' + tipColor + '.gif'} width='35' onClick={this.toggle} ref='target' diff --git a/web/react/components/user_settings/manage_incoming_hooks.jsx b/web/react/components/user_settings/manage_incoming_hooks.jsx index 6b8c09718..128c011ea 100644 --- a/web/react/components/user_settings/manage_incoming_hooks.jsx +++ b/web/react/components/user_settings/manage_incoming_hooks.jsx @@ -163,7 +163,7 @@ export default class ManageIncomingHooks extends React.Component { return ( <div key='addIncomingHook'> {'Create webhook URLs for use in external integrations. Please see '}<a href='http://mattermost.org/webhooks'>{'http://mattermost.org/webhooks'}</a> {' to learn more.'} - <label className='control-label padding-top x2'>{'Add a new incoming webhook'}</label> + <div><label className='control-label padding-top x2'>{'Add a new incoming webhook'}</label></div> <div className='row padding-top'> <div className='col-sm-10 padding-bottom'> <select diff --git a/web/react/components/user_settings/manage_outgoing_hooks.jsx b/web/react/components/user_settings/manage_outgoing_hooks.jsx index 93be988d1..7b7cf7401 100644 --- a/web/react/components/user_settings/manage_outgoing_hooks.jsx +++ b/web/react/components/user_settings/manage_outgoing_hooks.jsx @@ -241,7 +241,7 @@ export default class ManageOutgoingHooks extends React.Component { return ( <div key='addOutgoingHook'> {'Create webhooks to send new message events to an external integration. Please see '}<a href='http://mattermost.org/webhooks'>{'http://mattermost.org/webhooks'}</a> {' to learn more.'} - <label className='control-label'>{'Add a new outgoing webhook'}</label> + <div><label className='control-label padding-top x2'>{'Add a new outgoing webhook'}</label></div> <div className='padding-top divider-light'></div> <div className='padding-top'> <div> diff --git a/web/sass-files/sass/partials/_tutorial.scss b/web/sass-files/sass/partials/_tutorial.scss index 08d491fd9..cfbc3454a 100644 --- a/web/sass-files/sass/partials/_tutorial.scss +++ b/web/sass-files/sass/partials/_tutorial.scss @@ -127,7 +127,11 @@ right:0px; &.tip-overlay--header { - top: 20px; + top: 21px; + right: 2px; + .tip-button { + @include opacity(0.8); + } } &.tip-overlay--sidebar { @@ -154,11 +158,11 @@ margin-bottom: 50px; text-align: left; display: inline-block; - padding-bottom: 30px; + padding-bottom: 30px; } .btn-primary { position: absolute; - bottom: 0; + bottom: 0px; } } h1 { @@ -175,6 +179,11 @@ position: absolute; bottom: 40px; } + .tutorial-skip { + position: absolute; + bottom: 7px; + left: 80px; + } } .tutorial__circles { diff --git a/web/static/images/tutorialTip.gif b/web/static/images/tutorialTip.gif Binary files differindex f185ff4b9..c925f0222 100644 --- a/web/static/images/tutorialTip.gif +++ b/web/static/images/tutorialTip.gif diff --git a/web/static/images/tutorialTipWhite.gif b/web/static/images/tutorialTipWhite.gif Binary files differnew file mode 100644 index 000000000..ecf8e97be --- /dev/null +++ b/web/static/images/tutorialTipWhite.gif |