diff options
33 files changed, 570 insertions, 401 deletions
diff --git a/api/file.go b/api/file.go index 5dc1db650..bb9aa00d8 100644 --- a/api/file.go +++ b/api/file.go @@ -23,7 +23,6 @@ import ( "image/jpeg" "io" "io/ioutil" - "mime" "net/http" "net/url" "os" @@ -407,7 +406,7 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "max-age=2592000, public") w.Header().Set("Content-Length", strconv.Itoa(len(f))) - w.Header().Set("Content-Type", mime.TypeByExtension(filepath.Ext(filename))) + w.Header().Del("Content-Type") // Content-Type will be set automatically by the http writer // attach extra headers to trigger a download on IE and Edge ua := user_agent.New(r.UserAgent()) diff --git a/doc/README.md b/doc/README.md index d711b5d53..66af8f34c 100644 --- a/doc/README.md +++ b/doc/README.md @@ -19,6 +19,7 @@ - [Code Contribution Guidelines](developer/Code-Contribution-Guidelines.md) - [Developer Machine Setup](developer/Setup.md) - [Mattermost Style Guide](developer/Style-Guide.md) +- [API Overview] (api/Overview.md) ## Usage Help diff --git a/doc/api/Overview.md b/doc/api/Overview.md new file mode 100644 index 000000000..02e11974e --- /dev/null +++ b/doc/api/Overview.md @@ -0,0 +1,94 @@ +# API Overview + +This provides a basic overview of the Mattermost API. All examples assume there is a Mattermost instance running at http://localhost:8065. + +## Schema + +All API access is done through `yourdomain.com/api/v1/`, with all data being sent and received as JSON. + + +## Authentication + +The majority of the Mattermost API involves interacting with teams. Therefore, most API methods require authentication as a user. There are two ways to authenticate into a Mattermost system. + +##### Session Token + +Make an HTTP POST to `yourdomain.com/api/v1/users/login` with a JSON body indicating the `name` of the team, the user's `email` and `password`. + +``` +curl -i -d '{"name":"exampleteam","email":"someone@nowhere.com","password":"thisisabadpassword"}' http://localhost:8065/api/v1/users/login +``` + +If successful, the response will contain a `Token` header and a User object in the body. + +``` +HTTP/1.1 200 OK +Set-Cookie: MMSID=hyr5dmb1mbb49c44qmx4whniso; Path=/; Max-Age=2592000; HttpOnly +Token: hyr5dmb1mbb49c44qmx4whniso +X-Ratelimit-Limit: 10 +X-Ratelimit-Remaining: 9 +X-Ratelimit-Reset: 1 +X-Request-Id: smda55ckcfy89b6tia58shk5fh +X-Version-Id: developer +Date: Fri, 11 Sep 2015 13:21:14 GMT +Content-Length: 657 +Content-Type: application/json; charset=utf-8 + +{{user object as json}} +``` + +Include the `Token` as part of the `Authentication` header on your future API requests with the `Bearer` method. + +``` +curl -i -H 'Authorization: Bearer hyr5dmb1mbb49c44qmx4whniso' http://localhost:8065/api/v1/users/me +``` + +That's it! You should now be able to access the API as the user you logged in as. + +##### OAuth2 + +Coming soon... + + +## Client Errors + +All errors will return an appropriate HTTP response code along with the following JSON body: + +``` +{ + "message": "", // the reason for the error + "detailed_error": "", // some extra details about the error + "request_id": "", // the ID of the request + "status_code": 0 // the HTTP status code +} +``` + + +## Rate Limiting + +Whenever you make an HTTP request to the Mattermost API you might notice the following headers included in the response: +``` +X-Ratelimit-Limit: 10 +X-Ratelimit-Remaining: 9 +X-Ratelimit-Reset: 1441983590 + +``` + +These headers are telling you your current rate limit status. + +Header | Description +--------------------- | ----------- +X-Ratelimit-Limit | The maximum number of requests you can make per second. +X-Ratelimit-Remaining | The number of requests remaining in the current window. +X-Ratelimit-Reset | The remaining UTC epoch seconds before the rate limit resets. + +If you exceed your rate limit for a window you will receive the following error in the body of the response: +``` +HTTP/1.1 429 Too Many Requests +Date: Tue, 10 Sep 2015 11:20:28 GMT +X-RateLimit-Limit: 10 +X-RateLimit-Remaining: 0 +X-RateLimit-Reset: 1 + +limit exceeded +``` diff --git a/doc/install/Production-Ubuntu.md b/doc/install/Production-Ubuntu.md index 0f1373c1a..05a56c412 100644 --- a/doc/install/Production-Ubuntu.md +++ b/doc/install/Production-Ubuntu.md @@ -8,7 +8,7 @@ ## Set up Database Server 1. For the purposes of this guide we will assume this server has an IP address of 10.10.10.1 -1. Install PostgreSQL 9.3+ (or MySQL 5.2+) +1. Install PostgreSQL 9.3+ (or MySQL 5.6+) * ``` sudo apt-get install postgresql postgresql-contrib``` 1. PostgreSQL created a user account called `postgres`. You will need to log into that account with: * ``` sudo -i -u postgres``` diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx index aee2541b5..ff370c32e 100644 --- a/web/react/components/activity_log_modal.jsx +++ b/web/react/components/activity_log_modal.jsx @@ -31,6 +31,11 @@ export default class ActivityLogModal extends React.Component { } submitRevoke(altId, e) { e.preventDefault(); + var modalContent = $(e.target).closest('.modal-content'); + modalContent.addClass('animation--highlight'); + setTimeout(() => { + modalContent.removeClass('animation--highlight'); + }, 1500); Client.revokeSession(altId, function handleRevokeSuccess() { AsyncClient.getSessions(); diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx index 92f0bbdce..f40e48f70 100644 --- a/web/react/components/admin_console/admin_controller.jsx +++ b/web/react/components/admin_console/admin_controller.jsx @@ -147,7 +147,7 @@ export default class AdminController extends React.Component { } return ( - <div className='container-fluid'> + <div> <div className='sidebar--menu' id='sidebar-menu' diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx index 4b9ff3cb8..f102661b2 100644 --- a/web/react/components/admin_console/admin_sidebar.jsx +++ b/web/react/components/admin_console/admin_sidebar.jsx @@ -129,141 +129,143 @@ export default class AdminSidebar extends React.Component { <div className='sidebar--left sidebar--collapsable'> <div> <AdminSidebarHeader /> - <ul className='nav nav-pills nav-stacked'> - <li> - <ul className='nav nav__sub-menu'> - <li> - <h4> - <span className='icon fa fa-gear'></span> - <span>{'SETTINGS'}</span> - </h4> - </li> - </ul> - <ul className='nav nav__sub-menu padded'> - <li> - <a - href='#' - className={this.isSelected('service_settings')} - onClick={this.handleClick.bind(this, 'service_settings', null)} - > - {'Service Settings'} - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('team_settings')} - onClick={this.handleClick.bind(this, 'team_settings', null)} - > - {'Team Settings'} - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('sql_settings')} - onClick={this.handleClick.bind(this, 'sql_settings', null)} - > - {'SQL Settings'} - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('email_settings')} - onClick={this.handleClick.bind(this, 'email_settings', null)} - > - {'Email Settings'} - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('image_settings')} - onClick={this.handleClick.bind(this, 'image_settings', null)} - > - {'File Settings'} - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('log_settings')} - onClick={this.handleClick.bind(this, 'log_settings', null)} - > - {'Log Settings'} - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('rate_settings')} - onClick={this.handleClick.bind(this, 'rate_settings', null)} - > - {'Rate Limit Settings'} - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('privacy_settings')} - onClick={this.handleClick.bind(this, 'privacy_settings', null)} - > - {'Privacy Settings'} - </a> - </li> - <li> - <a - href='#' - className={this.isSelected('gitlab_settings')} - onClick={this.handleClick.bind(this, 'gitlab_settings', null)} - > - {'GitLab Settings'} - </a> - </li> - </ul> - <ul className='nav nav__sub-menu'> - <li> - <h4> - <span className='icon fa fa-gear'></span> - <span>{'TEAMS (' + count + ')'}</span> - <span className='menu-icon--right'> - <a - href='#' - onClick={this.showTeamSelect} - > - <i className='fa fa-plus'></i> - </a> - </span> - </h4> - </li> - </ul> - <ul className='nav nav__sub-menu padded'> - <li> - {teams} - </li> - </ul> - <ul className='nav nav__sub-menu'> - <li> - <h4> - <span className='icon fa fa-gear'></span> - <span>{'OTHER'}</span> - </h4> - </li> - </ul> - <ul className='nav nav__sub-menu padded'> - <li> - <a - href='#' - className={this.isSelected('logs')} - onClick={this.handleClick.bind(this, 'logs', null)} - > - {'Logs'} - </a> - </li> - </ul> - </li> - </ul> + <div className='nav-pills__container'> + <ul className='nav nav-pills nav-stacked'> + <li> + <ul className='nav nav__sub-menu'> + <li> + <h4> + <span className='icon fa fa-gear'></span> + <span>{'SETTINGS'}</span> + </h4> + </li> + </ul> + <ul className='nav nav__sub-menu padded'> + <li> + <a + href='#' + className={this.isSelected('service_settings')} + onClick={this.handleClick.bind(this, 'service_settings', null)} + > + {'Service Settings'} + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('team_settings')} + onClick={this.handleClick.bind(this, 'team_settings', null)} + > + {'Team Settings'} + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('sql_settings')} + onClick={this.handleClick.bind(this, 'sql_settings', null)} + > + {'SQL Settings'} + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('email_settings')} + onClick={this.handleClick.bind(this, 'email_settings', null)} + > + {'Email Settings'} + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('image_settings')} + onClick={this.handleClick.bind(this, 'image_settings', null)} + > + {'File Settings'} + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('log_settings')} + onClick={this.handleClick.bind(this, 'log_settings', null)} + > + {'Log Settings'} + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('rate_settings')} + onClick={this.handleClick.bind(this, 'rate_settings', null)} + > + {'Rate Limit Settings'} + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('privacy_settings')} + onClick={this.handleClick.bind(this, 'privacy_settings', null)} + > + {'Privacy Settings'} + </a> + </li> + <li> + <a + href='#' + className={this.isSelected('gitlab_settings')} + onClick={this.handleClick.bind(this, 'gitlab_settings', null)} + > + {'GitLab Settings'} + </a> + </li> + </ul> + <ul className='nav nav__sub-menu'> + <li> + <h4> + <span className='icon fa fa-gear'></span> + <span>{'TEAMS (' + count + ')'}</span> + <span className='menu-icon--right'> + <a + href='#' + onClick={this.showTeamSelect} + > + <i className='fa fa-plus'></i> + </a> + </span> + </h4> + </li> + </ul> + <ul className='nav nav__sub-menu padded'> + <li> + {teams} + </li> + </ul> + <ul className='nav nav__sub-menu'> + <li> + <h4> + <span className='icon fa fa-gear'></span> + <span>{'OTHER'}</span> + </h4> + </li> + </ul> + <ul className='nav nav__sub-menu padded'> + <li> + <a + href='#' + className={this.isSelected('logs')} + onClick={this.handleClick.bind(this, 'logs', null)} + > + {'Logs'} + </a> + </li> + </ul> + </li> + </ul> + </div> </div> <SelectTeamModal diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx index 762a4ab26..3432f69ff 100644 --- a/web/react/components/admin_console/email_settings.jsx +++ b/web/react/components/admin_console/email_settings.jsx @@ -437,7 +437,7 @@ export default class EmailSettings extends React.Component { </select> <div className='help-text'> <table - className='table-bordered' + className='table table-bordered' cellPadding='5' > <tr><td className='help-text'>{'None'}</td><td className='help-text'>{'Mattermost will send email over an unsecure connection.'}</td></tr> @@ -447,7 +447,7 @@ export default class EmailSettings extends React.Component { </div> <div className='help-text'> <button - className='help-link' + className='btn' onClick={this.handleTestConnection} disabled={!this.state.sendEmailNotifications} id='connection-button' @@ -482,7 +482,7 @@ export default class EmailSettings extends React.Component { <p className='help-text'>{'32-character salt added to signing of email invites. Randomly generated on install. Click "Re-Generate" to create new salt.'}</p> <div className='help-text'> <button - className='help-link' + className='btn' onClick={this.handleGenerateInvite} disabled={!this.state.sendEmailNotifications} > @@ -513,7 +513,7 @@ export default class EmailSettings extends React.Component { <p className='help-text'>{'32-character salt added to signing of password reset emails. Randomly generated on install. Click "Re-Generate" to create new salt.'}</p> <div className='help-text'> <button - className='help-link' + className='btn' onClick={this.handleGenerateReset} disabled={!this.state.sendEmailNotifications} > diff --git a/web/react/components/admin_console/image_settings.jsx b/web/react/components/admin_console/image_settings.jsx index e52f516e8..e08d39ca8 100644 --- a/web/react/components/admin_console/image_settings.jsx +++ b/web/react/components/admin_console/image_settings.jsx @@ -460,7 +460,7 @@ export default class FileSettings extends React.Component { <p className='help-text'>{'32-character salt added to signing of public image links. Randomly generated on install. Click "Re-Generate" to create new salt.'}</p> <div className='help-text'> <button - className='help-link' + className='btn btn-default' onClick={this.handleGenerate} > {'Re-Generate'} diff --git a/web/react/components/admin_console/log_settings.jsx b/web/react/components/admin_console/log_settings.jsx index 1c39c60e8..608ef9cc0 100644 --- a/web/react/components/admin_console/log_settings.jsx +++ b/web/react/components/admin_console/log_settings.jsx @@ -253,7 +253,7 @@ export default class LogSettings extends React.Component { {'Format of log message output. If blank will be set to "[%D %T] [%L] %M", where:'} <div className='help-text'> <table - className='table-bordered' + className='table table-bordered' cellPadding='5' > <tr><td className='help-text'>{'%T'}</td><td className='help-text'>{'Time (15:04:05 MST)'}</td></tr> diff --git a/web/react/components/email_verify.jsx b/web/react/components/email_verify.jsx index 8d3f15525..391de3326 100644 --- a/web/react/components/email_verify.jsx +++ b/web/react/components/email_verify.jsx @@ -38,8 +38,8 @@ export default class EmailVerify extends React.Component { } return ( - <div className='col-sm-offset-4 col-sm-4'> - <div className='panel panel-default'> + <div className='col-sm-12'> + <div className='panel panel-default verify_panel'> <div className='panel-heading'> <h3 className='panel-title'>{title}</h3> </div> diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx index 1d4aac3e6..6e0728862 100644 --- a/web/react/components/get_link_modal.jsx +++ b/web/react/components/get_link_modal.jsx @@ -23,7 +23,7 @@ export default class GetLinkModal extends React.Component { componentDidMount() { if (this.refs.modal) { $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); - $(React.findDOMNode(this.refs.modal)).on('hide.bs.modal', this.onShow); + $(React.findDOMNode(this.refs.modal)).on('hide.bs.modal', this.onHide); } } handleClick() { @@ -43,8 +43,23 @@ export default class GetLinkModal extends React.Component { } render() { var currentUser = UserStore.getCurrentUser(); - var copyLinkConfirm = null; + let copyLink = null; + if (document.queryCommandSupported('copy')) { + copyLink = ( + <button + data-copy-btn='true' + type='button' + className='btn btn-primary pull-left' + onClick={this.handleClick} + data-clipboard-text={this.state.value} + > + Copy Link + </button> + ); + } + + var copyLinkConfirm = null; if (this.state.copiedLink) { copyLinkConfirm = <p className='alert alert-success copy-link-confirm'><i className='fa fa-check'></i> Link copied to clipboard.</p>; } @@ -98,15 +113,7 @@ export default class GetLinkModal extends React.Component { > Close </button> - <button - data-copy-btn='true' - type='button' - className='btn btn-primary pull-left' - onClick={this.handleClick} - data-clipboard-text={this.state.value} - > - Copy Link - </button> + {copyLink} {copyLinkConfirm} </div> </div> diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx index 70f7a5d6e..54df75cbc 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -112,6 +112,17 @@ export default class Login extends React.Component { errorClass = ' has-error'; } + const verifiedParam = Utils.getUrlParameter('verified'); + let verifiedBox = ''; + if (verifiedParam) { + verifiedBox = ( + <div className='alert alert-success'> + <i className='fa fa-check' /> + {' Email Verified'} + </div> + ); + } + let emailSignup; if (global.window.config.EnableSignUpWithEmail === 'true') { emailSignup = ( @@ -175,6 +186,7 @@ export default class Login extends React.Component { <h2 className='signup-team__name'>{teamDisplayName}</h2> <h2 className='signup-team__subdomain'>on {global.window.config.SiteName}</h2> <form onSubmit={this.handleSubmit}> + {verifiedBox} <div className={'form-group' + errorClass}> {serverError} </div> diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index ae3075495..4e17c6d06 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -20,8 +20,6 @@ export default class SignupUserComplete extends React.Component { initialState.user = {}; initialState.user.team_id = this.props.teamId; initialState.user.email = this.props.email; - initialState.hash = this.props.hash; - initialState.data = this.props.data; initialState.original_email = this.props.email; } @@ -47,7 +45,7 @@ export default class SignupUserComplete extends React.Component { return; } - const usernameError = Utils.isValidUsername(this.state.user.username); + const usernameError = Utils.isValidUsername(providedUsername); if (usernameError === 'Cannot use a reserved word as a username.') { this.setState({nameError: 'This username is reserved, please choose a new one.', emailError: '', passwordError: '', serverError: ''}); return; @@ -67,26 +65,29 @@ export default class SignupUserComplete extends React.Component { return; } + const user = { + team_id: this.props.teamId, + email: providedEmail, + username: providedUsername, + password: providedPassword, + allow_marketing: true + }; + this.setState({ - user: { - email: providedEmail, - username: providedUsername, - password: providedPassword, - allow_marketing: true - }, + user, nameError: '', emailError: '', passwordError: '', serverError: '' }); - client.createUser(this.state.user, this.state.data, this.state.hash, + client.createUser(user, this.props.data, this.props.hash, function createUserSuccess() { client.track('signup', 'signup_user_02_complete'); - client.loginByEmail(this.props.teamName, this.state.user.email, this.state.user.password, + client.loginByEmail(this.props.teamName, user.email, user.password, function emailLoginSuccess(data) { - UserStore.setLastEmail(this.state.user.email); + UserStore.setLastEmail(user.email); UserStore.setCurrentUser(data); if (this.props.hash > 0) { BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: 'finished'})); @@ -95,7 +96,7 @@ export default class SignupUserComplete extends React.Component { }.bind(this), function emailLoginFailure(err) { if (err.message === 'Login failed because email address has not been verified') { - window.location.href = '/verify_email?email=' + encodeURIComponent(this.state.user.email) + '&teamname=' + encodeURIComponent(this.props.teamName); + window.location.href = '/verify_email?email=' + encodeURIComponent(user.email) + '&teamname=' + encodeURIComponent(this.props.teamName); } else { this.setState({serverError: err.message}); } diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx index 4e8ee03fa..3301c6596 100644 --- a/web/react/components/user_settings/import_theme_modal.jsx +++ b/web/react/components/user_settings/import_theme_modal.jsx @@ -49,7 +49,6 @@ export default class ImportThemeModal extends React.Component { theme.sidebarText = colors[5]; theme.sidebarUnreadText = colors[5]; theme.sidebarTextHoverBg = colors[4]; - theme.sidebarTextHoverColor = colors[5]; theme.sidebarTextActiveBg = colors[2]; theme.sidebarTextActiveColor = colors[3]; theme.sidebarHeaderBg = colors[1]; diff --git a/web/react/package.json b/web/react/package.json index 31295873b..a097b6aa8 100644 --- a/web/react/package.json +++ b/web/react/package.json @@ -10,7 +10,6 @@ "keymirror": "0.1.1", "marked": "0.3.5", "object-assign": "3.0.0", - "react-zeroclipboard-mixin": "0.1.0", "twemoji": "1.4.1" }, "devDependencies": { diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 67414dc3b..8fe1a5884 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -122,10 +122,9 @@ module.exports = { default: { type: 'Mattermost', sidebarBg: '#fafafa', - sidebarText: '#999999', + sidebarText: '#333333', sidebarUnreadText: '#333333', sidebarTextHoverBg: '#e6f2fa', - sidebarTextHoverColor: '#999999', sidebarTextActiveBg: '#e1e1e1', sidebarTextActiveColor: '#111111', sidebarHeaderBg: '#2389d7', @@ -138,15 +137,15 @@ module.exports = { newMessageSeparator: '#FF8800', linkColor: '#2389d7', buttonBg: '#2389d7', - buttonColor: '#FFFFFF' + buttonColor: '#FFFFFF', + mentionHighlightBg: '#fff2bb' }, organization: { type: 'Organization', sidebarBg: '#2071a7', - sidebarText: '#bfcde8', + sidebarText: '#fff', sidebarUnreadText: '#fff', sidebarTextHoverBg: '#136197', - sidebarTextHoverColor: '#bfcde8', sidebarTextActiveBg: '#136197', sidebarTextActiveColor: '#FFFFFF', sidebarHeaderBg: '#2f81b7', @@ -159,15 +158,15 @@ module.exports = { newMessageSeparator: '#FF8800', linkColor: '#2f81b7', buttonBg: '#1dacfc', - buttonColor: '#FFFFFF' + buttonColor: '#FFFFFF', + mentionHighlightBg: '#fff2bb' }, mattermostDark: { type: 'Mattermost Dark', sidebarBg: '#1B2C3E', - sidebarText: '#bbbbbb', + sidebarText: '#fff', sidebarUnreadText: '#fff', sidebarTextHoverBg: '#4A5664', - sidebarTextHoverColor: '#bbbbbb', sidebarTextActiveBg: '#39769C', sidebarTextActiveColor: '#FFFFFF', sidebarHeaderBg: '#1B2C3E', @@ -180,15 +179,15 @@ module.exports = { newMessageSeparator: '#5de5da', linkColor: '#A4FFEB', buttonBg: '#4CBBA4', - buttonColor: '#FFFFFF' + buttonColor: '#FFFFFF', + mentionHighlightBg: '#338886' }, windows10: { type: 'Windows Dark', sidebarBg: '#171717', - sidebarText: '#eee', + sidebarText: '#fff', sidebarUnreadText: '#fff', sidebarTextHoverBg: '#302e30', - sidebarTextHoverColor: '#fff', sidebarTextActiveBg: '#484748', sidebarTextActiveColor: '#FFFFFF', sidebarHeaderBg: '#1f1f1f', @@ -201,7 +200,8 @@ module.exports = { newMessageSeparator: '#CC992D', linkColor: '#0177e7', buttonBg: '#0177e7', - buttonColor: '#FFFFFF' + buttonColor: '#FFFFFF', + mentionHighlightBg: '#276198' } }, THEME_ELEMENTS: [ @@ -230,10 +230,6 @@ module.exports = { uiName: 'Sidebar Text Hover BG' }, { - id: 'sidebarTextHoverColor', - uiName: 'Sidebar Text Hover Color' - }, - { id: 'sidebarTextActiveBg', uiName: 'Sidebar Text Active BG' }, @@ -263,7 +259,7 @@ module.exports = { }, { id: 'newMessageSeparator', - uiName: 'New message separator' + uiName: 'New Message Separator' }, { id: 'linkColor', @@ -273,10 +269,13 @@ module.exports = { id: 'buttonBg', uiName: 'Button BG' }, - { id: 'buttonColor', uiName: 'Button Text' + }, + { + id: 'mentionHighlightBg', + uiName: 'Mention Highlight BG' } ] }; diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 307a311ab..8431e56bc 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -399,9 +399,9 @@ export function applyTheme(theme) { } if (theme.sidebarText) { - changeCss('.sidebar--left .nav-pills__container li>a, .sidebar--right, .settings-modal .nav-pills>li a, .sidebar--menu', 'color:' + theme.sidebarText, 1); + changeCss('.sidebar--left .nav-pills__container li>a, .sidebar--right, .settings-modal .nav-pills>li a, .sidebar--menu', 'color:' + changeOpacity(theme.sidebarText, 0.6), 1); changeCss('@media(max-width: 768px){.settings-modal .settings-table .nav>li>a', 'color:' + theme.sidebarText, 1); - changeCss('.sidebar--left .nav-pills__container li>h4, .sidebar--left .add-channel-btn', 'color:' + changeOpacity(theme.sidebarText, 0.8), 1); + changeCss('.sidebar--left .nav-pills__container li>h4, .sidebar--left .add-channel-btn', 'color:' + changeOpacity(theme.sidebarText, 0.6), 1); changeCss('.sidebar--left .add-channel-btn:hover, .sidebar--left .add-channel-btn:focus', 'color:' + theme.sidebarText, 1); changeCss('.sidebar--left, .sidebar--right .sidebar--right__header', 'border-color:' + changeOpacity(theme.sidebarText, 0.2), 1); changeCss('.sidebar--left .status path', 'fill:' + changeOpacity(theme.sidebarText, 0.5), 1); @@ -417,17 +417,13 @@ export function applyTheme(theme) { changeCss('@media(max-width: 768px){.settings-modal .settings-table .nav>li:hover a', 'background:' + theme.sidebarTextHoverBg, 1); } - if (theme.sidebarTextHoverColor) { - changeCss('.sidebar--left .nav-pills__container li>a:hover, .sidebar--left .nav-pills__container li>a:focus, .settings-modal .nav-pills>li:hover a, .settings-modal .nav-pills>li:focus a', 'color:' + theme.sidebarTextHoverColor, 2); - changeCss('@media(max-width: 768px){.settings-modal .settings-table .nav>li:hover a', 'color:' + theme.sidebarTextHoverColor, 2); - } - if (theme.sidebarTextActiveBg) { changeCss('.sidebar--left .nav-pills__container li.active a, .sidebar--left .nav-pills__container li.active a:hover, .sidebar--left .nav-pills__container li.active a:focus, .settings-modal .nav-pills>li.active a, .settings-modal .nav-pills>li.active a:hover, .settings-modal .nav-pills>li.active a:active', 'background:' + theme.sidebarTextActiveBg, 1); } if (theme.sidebarTextActiveColor) { changeCss('.sidebar--left .nav-pills__container li.active a, .sidebar--left .nav-pills__container li.active a:hover, .sidebar--left .nav-pills__container li.active a:focus, .settings-modal .nav-pills>li.active a, .settings-modal .nav-pills>li.active a:hover, .settings-modal .nav-pills>li.active a:active', 'color:' + theme.sidebarTextActiveColor, 2); + changeCss('.sidebar--left .nav-pills__container li.active a .status .online--icon', 'fill:' + theme.sidebarTextActiveColor, 2); } if (theme.sidebarHeaderBg) { @@ -468,6 +464,7 @@ export function applyTheme(theme) { changeCss('.date-separator .separator__text, .new-separator .separator__text', 'background:' + theme.centerChannelBg, 1); changeCss('.post-image__column .post-image__details', 'background:' + theme.centerChannelBg, 1); changeCss('.sidebar--right, .dropdown-menu, .popover', 'background:' + theme.centerChannelBg, 1); + changeCss('.search-bar__container .search__form .search-bar, .form-control', 'background:' + theme.centerChannelBg, 1); } if (theme.centerChannelColor) { @@ -491,20 +488,21 @@ export function applyTheme(theme) { changeCss('.post-image__column', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 2); changeCss('.post-image__column .post-image__details', 'color:' + theme.centerChannelColor, 2); changeCss('.post-image__column a, .post-image__column a:hover, .post-image__column a:focus', 'color:' + theme.centerChannelColor, 1); - changeCss('.search-bar__container .search__form .search-bar', 'background: transparent; color:' + theme.centerChannelColor, 1); + changeCss('.search-bar__container .search__form .search-bar, .form-control', 'color:' + theme.centerChannelColor, 2); changeCss('@media(max-width: 768px){.search-bar__container .search__form .search-bar', 'background:' + changeOpacity(theme.centerChannelColor, 0.2) + '; color: inherit;', 1); - changeCss('.search-bar__container .search__form', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); + changeCss('.input-group-addon, .search-bar__container .search__form, .form-control', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); + changeCss('.form-control:focus', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1); changeCss('.channel-intro .channel-intro__content', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1); changeCss('.date-separator .separator__text', 'color:' + theme.centerChannelColor, 2); changeCss('.date-separator .separator__hr, .modal-footer, .modal .custom-textarea, .post-right__container .post.post--root hr, .search-item-container', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); changeCss('.modal .custom-textarea:focus', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1); changeCss('.channel-intro, .settings-modal .settings-table .settings-content .divider-dark, hr, .settings-modal .settings-table .settings-links', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); - changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment, .post.post--comment.other--root .post-comment, .post.same--root .post-body', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); + changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment, .post.post--comment.other--root .post-comment, .post.same--root .post-body, .modal .more-channel-table tbody>tr td, .member-div:first-child, .member-div, .access-history__table .access__report, .activity-log__table', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.1), 2); changeCss('@media(max-width: 1440px){.post.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); changeCss('@media(max-width: 1440px){.post.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); changeCss('@media(max-width: 1800px){.inner__wrap.move--left .post.post--comment.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); - changeCss('.post:hover, .sidebar--right .sidebar--right__header, .settings-modal .settings-table .settings-content .section-min:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); + changeCss('.post:hover, .modal .more-channel-table tbody>tr:hover td, .sidebar--right .sidebar--right__header, .settings-modal .settings-table .settings-content .section-min:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); changeCss('.date-separator.hovered--before:after, .date-separator.hovered--after:before, .new-separator.hovered--after:before, .new-separator.hovered--before:after', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); changeCss('.command-name:hover, .mentions-name:hover, .mentions-focus, .dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.15), 1); changeCss('.post.current--user:hover .post-body ', 'background: none;', 1); @@ -529,6 +527,10 @@ export function applyTheme(theme) { if (theme.buttonColor) { changeCss('.btn.btn-primary', 'color:' + theme.buttonColor, 2); } + + if (theme.mentionHighlightBg) { + changeCss('.mention-highlight, .search-highlight', 'background:' + theme.mentionHighlightBg, 1); + } } export function changeCss(className, classValue, classRepeat) { // we need invisible container to store additional css definitions diff --git a/web/sass-files/sass/partials/_access-history.scss b/web/sass-files/sass/partials/_access-history.scss index 412a2a1d0..a3289ecc0 100644 --- a/web/sass-files/sass/partials/_access-history.scss +++ b/web/sass-files/sass/partials/_access-history.scss @@ -24,6 +24,6 @@ font-size: 15px; } .report__info { - color: #999; + @include opacity(0.8); } }
\ No newline at end of file diff --git a/web/sass-files/sass/partials/_activity-log.scss b/web/sass-files/sass/partials/_activity-log.scss index 3f0c3090d..2fb37a3bb 100644 --- a/web/sass-files/sass/partials/_activity-log.scss +++ b/web/sass-files/sass/partials/_activity-log.scss @@ -1,3 +1,20 @@ +@keyframes highlight { + from { background: rgba(yellow, 0.5);} + to { background: none;} +} + +.animation--highlight { + &:before { + content: ''; + animation: highlight 1.5s ease; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } +} + .activity-log__table { display: table; width: 100%; @@ -26,7 +43,7 @@ } } .report__info { - color: #999; + @include opacity(0.8); } } .session-help-text { diff --git a/web/sass-files/sass/partials/_admin-console.scss b/web/sass-files/sass/partials/_admin-console.scss index 5037da415..09907da6d 100644 --- a/web/sass-files/sass/partials/_admin-console.scss +++ b/web/sass-files/sass/partials/_admin-console.scss @@ -1,193 +1,202 @@ - -.sidebar--left { &.sidebar--collapsable { - background: #333; - .team__header { - background: transparent; - margin-bottom: 5px; - } - .nav { - li { - padding: 0; - .icon { - width: 17px; - } - > a { - color: #fff; - padding: 9px 15px; - display: block; - &:hover, &.active, &:focus { - background-color: $primary-color; - } - } - > h4 { - background: #333; - padding: 10px 10px; - margin: 1px 0 0; - } - } - .menu-icon--right { - vertical-align: top; - padding: 5px 10px; - margin: -5px; - float: right; - .fa { - font-size: 13px; - right: -2px; - position: relative; - color: #fff; - } +#admin_controller { + > div { + height: 100%; + } + .table { + background: #fff; + } + .sidebar--left { + &.sidebar--collapsable { + background: #333; + .team__header { + background: transparent; + margin-bottom: 5px; } - &.nav__sub-menu { - background: #111; - -webkit-font-smoothing: auto; - &.padded { - padding: 5px 0; - } + .nav { li { + padding: 0; + .icon { + width: 17px; + } > a { - font-size: 13px; - padding: 5px 15px; - background: transparent; - color: #bbb; - &:hover { - color: lighten($primary-color, 10); - } - &.active { - color: #fff; - font-weight: 600; + padding: 9px 15px; + display: block; + &:hover, &.active, &:focus { + background-color: #EAEAEA; } } - .nav-more { + > h4 { + background: #333; + padding: 10px 10px; + margin: 1px 0 0; + } + } + .menu-icon--right { + vertical-align: top; + padding: 5px 10px; + margin: -5px; + float: right; + .fa { font-size: 13px; - padding: 5px 15px; - background: transparent; - color: #bbb; - display: block; - cursor: pointer; - &:hover { - color: lighten($primary-color, 10); + right: -2px; + position: relative; + color: #fff; + } + } + &.nav__sub-menu { + background: #111; + -webkit-font-smoothing: auto; + &.padded { + padding: 5px 0; + } + li { + > a { + font-size: 13px; + padding: 5px 15px; + background: transparent; + color: #bbb; + &:hover { + color: lighten($primary-color, 10); + } + &.active { + color: #fff; + font-weight: 600; + } + } + .nav-more { + font-size: 13px; + padding: 5px 15px; + background: transparent; + color: #bbb; + display: block; + cursor: pointer; + &:hover { + color: lighten($primary-color, 10); + } } } } - } - &.nav__inner-menu { - li { - > a { - padding-left: 20px; + &.nav__inner-menu { + li { + > a { + padding-left: 20px; + } } } } } } -} -.log__panel { - overflow: scroll; - width: 100%; - height: 800px; - border: 1px solid #ddd; - margin-top: 10px; - padding: 5px; - background-color: white; -} - -.app__content { - &.admin { - overflow: auto; - background-color: #f1f1f1; - padding: 0 20px 20px; - min-height: 600px; - } - .wrapper--fixed { - max-width: 800px; + .log__panel { + overflow: scroll; + width: 100%; + height: 800px; + border: 1px solid #ddd; + margin-top: 10px; + padding: 5px; + background-color: white; } - .form-horizontal { - margin-top: 40px; - .control-label { - text-align: left; - padding-right: 0; - font-weight: 600; + + .app__content { + &.admin { + overflow: auto; + background-color: #f1f1f1; + padding: 0 20px 20px; + min-height: 600px; } - .form-group { - margin-bottom: 25px; + .wrapper--fixed { + max-width: 800px; } - .help-text { - margin: 10px 0 0 15px; - color: #777; - .help-link { - margin-right: 5px; + .form-horizontal { + margin-top: 40px; + .control-label { + text-align: left; + padding-right: 0; + font-weight: 600; } - .btn { - font-size: 13px; + .form-group { + margin-bottom: 25px; + } + .help-text { + margin: 10px 0 0 15px; + color: #777; + .help-link { + margin-right: 5px; + } + .btn { + font-size: 13px; + } + } + .alert { + display: inline-block; + padding: 5px 7px; + margin: 1em 0 0; + top: 1px; + position: relative; + .fa { + margin-right: 5px; + } } } - .alert { - display: inline-block; - padding: 5px 7px; - margin: 0; - top: 1px; - position: relative; - } - } - .banner { - background: #fff; - border: 1px solid #ddd; - padding: 0.7em 1.5em; - font-size: 0.95em; - margin: 2em 0; - .banner__heading { - font-size: 1.5em; + .banner { + background: #fff; + border: 1px solid #ddd; + padding: 0.7em 1.5em; + font-size: 0.95em; + margin: 2em 0; + .banner__heading { + font-size: 1.5em; + } + .banner__content { + width: 80%; + } } - .banner__content { - width: 80%; + .popover { + border-radius: 3px; + width: 100%; + font-size: 0.95em; } - } - .popover { - border-radius: 3px; - width: 100%; - font-size: 0.95em; - } - .panel { - border: none; - background-color: transparent; - } - .panel-default { - > .panel-heading { - padding: 10px 0; + .panel { + border: none; background-color: transparent; } - .panel-body { - padding: 30px 0 10px; + .panel-default { + > .panel-heading { + padding: 10px 0; + background-color: transparent; + } + .panel-body { + padding: 30px 0 10px; + } + } + .panel-group { + margin-bottom: 50px; } - } - .panel-group { - margin-bottom: 50px; - } - .panel-title { - font-size: 24px; - line-height: 1.5; - a { - text-decoration: none; - display: block; - @include clearfix; - &.collapsed { - .fa-minus { - display: none; + .panel-title { + font-size: 24px; + line-height: 1.5; + a { + text-decoration: none; + display: block; + @include clearfix; + &.collapsed { + .fa-minus { + display: none; + } + .fa-plus { + display: inline-block; + } + } + .fa { + font-size: 18px; + float: right; + margin-top: 8px; + color: #aaa; } .fa-plus { - display: inline-block; + display: none; } } - .fa { - font-size: 18px; - float: right; - margin-top: 8px; - color: #aaa; - } - .fa-plus { - display: none; - } } } - }
\ No newline at end of file diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss index 841db7c5a..18462d92a 100644 --- a/web/sass-files/sass/partials/_base.scss +++ b/web/sass-files/sass/partials/_base.scss @@ -34,6 +34,10 @@ body { } } +.input-group-addon { + background: transparent; +} + .popover { color: #333; &.bottom { @@ -97,6 +101,9 @@ a:focus, a:hover { .form-control { @include border-radius(2px); + &:focus { + @include box-shadow(none); + } &.no-resize { resize: none; } @@ -120,6 +127,10 @@ a:focus, a:hover { z-index: 100; } +.nav>li>a:focus, .nav>li>a:hover { + background: transparent; +} + .btn { @include single-transition(all, 0.25s, ease-in); @include border-radius(1px); diff --git a/web/sass-files/sass/partials/_headers.scss b/web/sass-files/sass/partials/_headers.scss index 2a4e82fc7..8e353aff9 100644 --- a/web/sass-files/sass/partials/_headers.scss +++ b/web/sass-files/sass/partials/_headers.scss @@ -37,7 +37,6 @@ &.description { overflow: hidden; text-overflow: ellipsis; - @include opacity(0.6); margin-top: 2px; max-height: 45px; } diff --git a/web/sass-files/sass/partials/_mentions.scss b/web/sass-files/sass/partials/_mentions.scss index a2bd0dcea..fb74eb4f5 100644 --- a/web/sass-files/sass/partials/_mentions.scss +++ b/web/sass-files/sass/partials/_mentions.scss @@ -57,5 +57,11 @@ .mention-highlight { background-color:#fff2bb; - color: #333; + a { + color: inherit; + text-decoration: underline; + &:hover, &:active { + color: inherit; + } + } }
\ No newline at end of file diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss index 96b26f251..2722333a4 100644 --- a/web/sass-files/sass/partials/_modal.scss +++ b/web/sass-files/sass/partials/_modal.scss @@ -144,11 +144,10 @@ font-size: 0.9em; overflow: hidden; text-overflow: ellipsis; - color: #999; + @include opacity(0.8); margin: 5px 0; } .more-channel-name { - color: #444; font-weight: 600; font-size: 0.95em; } diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss index 8bf4b0534..19cfdc050 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -257,7 +257,7 @@ body.ios { line-height: 18px; display: inline-block; font-size: 13px; - @include opacity(0.6); + @include opacity(0.7); } } } diff --git a/web/sass-files/sass/partials/_post_right.scss b/web/sass-files/sass/partials/_post_right.scss index da5bcbad2..e4860b286 100644 --- a/web/sass-files/sass/partials/_post_right.scss +++ b/web/sass-files/sass/partials/_post_right.scss @@ -29,7 +29,7 @@ min-height: 100px; } .msg-typing { - color: #555; + @include opacity(0.7); float: left; padding-top: 17px; } diff --git a/web/sass-files/sass/partials/_search.scss b/web/sass-files/sass/partials/_search.scss index bcb8b5eac..a7b1ab190 100644 --- a/web/sass-files/sass/partials/_search.scss +++ b/web/sass-files/sass/partials/_search.scss @@ -104,7 +104,13 @@ padding: 10px; } -.search-highlight.theme, .search-highlight { +.search-highlight { background-color: #FFF2BB; - color: #333; + a { + color: inherit; + text-decoration: underline; + &:hover, &:active { + color: inherit; + } + } } diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss index 42475efd5..9369cc097 100644 --- a/web/sass-files/sass/partials/_settings.scss +++ b/web/sass-files/sass/partials/_settings.scss @@ -240,10 +240,6 @@ margin-right:5px; } -.member-list-holder { - background-color:#fff; -} - .member-div { border-bottom:1px solid lightgrey; position:relative; diff --git a/web/sass-files/sass/partials/_signup.scss b/web/sass-files/sass/partials/_signup.scss index 924f0718a..fcf0d5d77 100644 --- a/web/sass-files/sass/partials/_signup.scss +++ b/web/sass-files/sass/partials/_signup.scss @@ -12,6 +12,9 @@ &.padding--less { padding-top: 50px; } + .form-control:focus { + @include box-shadow(inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)); + } h1, h2, h3, h4, h5, h6, p { line-height: 1.3; @@ -330,3 +333,8 @@ .authorize-btn { margin-right: 6px; } + +.verify_panel { + margin: 60px auto auto auto; + max-width: 380px; +} diff --git a/web/templates/admin_console.html b/web/templates/admin_console.html index 1444d9b17..a046478f6 100644 --- a/web/templates/admin_console.html +++ b/web/templates/admin_console.html @@ -5,15 +5,15 @@ {{template "head" . }} <body> -<div id="error_bar"></div> +<div id='error_bar'></div> -<div id="admin_controller"></div> +<div id='admin_controller' class='container-fluid'></div> -<div id="select_team_modal"></div> +<div id='select_team_modal'></div> <script> window.setup_admin_console_page(); - + $(document).ready(function(){ $('[data-toggle="tooltip"]').tooltip(); $('[data-toggle="popover"]').popover(); diff --git a/web/templates/verify.html b/web/templates/verify.html index cb4832512..a49ba7930 100644 --- a/web/templates/verify.html +++ b/web/templates/verify.html @@ -1,16 +1,21 @@ {{define "verify"}} <!DOCTYPE html> <html> -{{template "head" . }} -<body> - <div class="container-fluid"> - <div class="row"> - <div id="verify"></div> + {{template "head" . }} + <body class="white"> + <div class="container-fluid"> + <div class="inner__wrap"> + <div class="row content"> + <div id="verify"></div> + </div> + <div class="row footer"> + {{template "footer" . }} + </div> + </div> </div> - </div> - <script> - window.setupVerifyPage({{ .Props }}); - </script> -</body> + <script> + window.setupVerifyPage({{ .Props }}); + </script> + </body> </html> {{end}} diff --git a/web/web.go b/web/web.go index a1bbf5a81..b87636187 100644 --- a/web/web.go +++ b/web/web.go @@ -422,24 +422,17 @@ func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) { } } - var isVerified string - if len(userId) != 26 { - isVerified = "false" - } else if len(hashedId) == 0 { - isVerified = "false" - } else if model.ComparePassword(hashedId, userId) { - isVerified = "true" + if len(userId) == 26 && len(hashedId) != 0 && model.ComparePassword(hashedId, userId) { if c.Err = (<-api.Srv.Store.User().VerifyEmail(userId)).Err; c.Err != nil { return } else { - c.LogAudit("") + c.LogAudit("Email Verified") + http.Redirect(w, r, api.GetProtocol(r)+"://"+r.Host+"/"+name+"/login?verified=true&email="+email, http.StatusTemporaryRedirect) + return } - } else { - isVerified = "false" } page := NewHtmlTemplatePage("verify", "Email Verified") - page.Props["IsVerified"] = isVerified page.Props["TeamURL"] = c.GetTeamURLFromTeam(team) page.Props["UserEmail"] = email page.Props["ResendSuccess"] = resendSuccess |