diff options
Diffstat (limited to 'web/react')
65 files changed, 925 insertions, 571 deletions
diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx index bb43af802..68984c9e0 100644 --- a/web/react/components/admin_console/admin_controller.jsx +++ b/web/react/components/admin_console/admin_controller.jsx @@ -4,6 +4,7 @@ var AdminSidebar = require('./admin_sidebar.jsx'); var EmailTab = require('./email_settings.jsx'); var JobsTab = require('./jobs_settings.jsx'); +var LogsTab = require('./logs.jsx'); var Navbar = require('../../components/navbar.jsx'); export default class AdminController extends React.Component { @@ -28,6 +29,8 @@ export default class AdminController extends React.Component { tab = <EmailTab />; } else if (this.state.selected === 'job_settings') { tab = <JobsTab />; + } else if (this.state.selected === 'logs') { + tab = <LogsTab />; } return ( diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx index 6b3be89d0..a04bceef5 100644 --- a/web/react/components/admin_console/admin_sidebar.jsx +++ b/web/react/components/admin_console/admin_sidebar.jsx @@ -83,7 +83,15 @@ export default class AdminSidebar extends React.Component { {'Email Settings'} </a> </li> - <li><a href='#'>{'Other Settings'}</a></li> + <li> + <a + href='#' + className={this.isSelected('logs')} + onClick={this.handleClick.bind(null, 'logs')} + > + {'Logs'} + </a> + </li> </ul> </li> <li> diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx index 3c53a8ee1..e8fb25858 100644 --- a/web/react/components/admin_console/email_settings.jsx +++ b/web/react/components/admin_console/email_settings.jsx @@ -300,7 +300,7 @@ export default class EmailSettings extends React.Component { type='submit' className='btn btn-primary' > - {'Submit'} + {'Save'} </button> </div> </div> diff --git a/web/react/components/admin_console/jobs_settings.jsx b/web/react/components/admin_console/jobs_settings.jsx index 34ec9693d..0b4fc4185 100644 --- a/web/react/components/admin_console/jobs_settings.jsx +++ b/web/react/components/admin_console/jobs_settings.jsx @@ -172,7 +172,7 @@ export default class Jobs extends React.Component { type='submit' className='btn btn-primary' > - {'Submit'} + {'Save'} </button> </div> </div> diff --git a/web/react/components/admin_console/logs.jsx b/web/react/components/admin_console/logs.jsx new file mode 100644 index 000000000..d7de76a94 --- /dev/null +++ b/web/react/components/admin_console/logs.jsx @@ -0,0 +1,88 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var AdminStore = require('../../stores/admin_store.jsx'); +var LoadingScreen = require('../loading_screen.jsx'); +var AsyncClient = require('../../utils/async_client.jsx'); + +export default class Logs extends React.Component { + constructor(props) { + super(props); + + this.onLogListenerChange = this.onLogListenerChange.bind(this); + this.reload = this.reload.bind(this); + + this.state = { + logs: AdminStore.getLogs() + }; + } + + componentDidMount() { + AdminStore.addLogChangeListener(this.onLogListenerChange); + AsyncClient.getLogs(); + } + componentWillUnmount() { + AdminStore.removeLogChangeListener(this.onLogListenerChange); + } + onLogListenerChange() { + this.setState({ + logs: AdminStore.getLogs() + }); + } + + reload() { + AdminStore.saveLogs(null); + this.setState({ + logs: null + }); + + AsyncClient.getLogs(); + } + + render() { + var content = null; + + if (this.state.logs === null) { + content = <LoadingScreen />; + } else { + content = []; + + for (var i = 0; i < this.state.logs.length; i++) { + var style = { + whiteSpace: 'nowrap', + fontFamily: 'monospace' + }; + + if (this.state.logs[i].indexOf('[EROR]') > 0) { + style.color = 'red'; + } + + content.push(<br key={'br_' + i} />); + content.push( + <span + key={'log_' + i} + style={style} + > + {this.state.logs[i]} + </span> + ); + } + } + + return ( + <div className='panel'> + <h3>{'Server Logs'}</h3> + <button + type='submit' + className='btn btn-primary' + onClick={this.reload} + > + {'Reload'} + </button> + <div className='log__panel'> + {content} + </div> + </div> + ); + } +}
\ No newline at end of file diff --git a/web/react/components/authorize.jsx b/web/react/components/authorize.jsx new file mode 100644 index 000000000..dd4479ad4 --- /dev/null +++ b/web/react/components/authorize.jsx @@ -0,0 +1,72 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Client = require('../utils/client.jsx'); + +export default class Authorize extends React.Component { + constructor(props) { + super(props); + + this.handleAllow = this.handleAllow.bind(this); + this.handleDeny = this.handleDeny.bind(this); + + this.state = {}; + } + handleAllow() { + const responseType = this.props.responseType; + const clientId = this.props.clientId; + const redirectUri = this.props.redirectUri; + const state = this.props.state; + const scope = this.props.scope; + + Client.allowOAuth2(responseType, clientId, redirectUri, state, scope, + (data) => { + if (data.redirect) { + window.location.replace(data.redirect); + } + }, + () => {} + ); + } + handleDeny() { + window.location.replace(this.props.redirectUri + '?error=access_denied'); + } + render() { + return ( + <div className='authorize-box'> + <div className='authorize-inner'> + <h3>{'An application would like to connect to your '}{this.props.teamName}{' account'}</h3> + <label>{'The app '}{this.props.appName}{' would like the ability to access and modify your basic information.'}</label> + <br/> + <br/> + <label>{'Allow '}{this.props.appName}{' access?'}</label> + <br/> + <button + type='submit' + className='btn authorize-btn' + onClick={this.handleDeny} + > + {'Deny'} + </button> + <button + type='submit' + className='btn btn-primary authorize-btn' + onClick={this.handleAllow} + > + {'Allow'} + </button> + </div> + </div> + ); + } +} + +Authorize.propTypes = { + appName: React.PropTypes.string, + teamName: React.PropTypes.string, + responseType: React.PropTypes.string, + clientId: React.PropTypes.string, + redirectUri: React.PropTypes.string, + state: React.PropTypes.string, + scope: React.PropTypes.string +}; diff --git a/web/react/components/change_url_modal.jsx b/web/react/components/change_url_modal.jsx index 28fa70c1f..3553e1107 100644 --- a/web/react/components/change_url_modal.jsx +++ b/web/react/components/change_url_modal.jsx @@ -159,7 +159,7 @@ ChangeUrlModal.defaultProps = { title: 'Change URL', desciption: '', urlLabel: 'URL', - submitButtonText: 'Submit', + submitButtonText: 'Save', currentURL: '', serverError: '' }; diff --git a/web/react/components/email_verify.jsx b/web/react/components/email_verify.jsx index 95948c8dd..92123956f 100644 --- a/web/react/components/email_verify.jsx +++ b/web/react/components/email_verify.jsx @@ -1,8 +1,6 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -import {config} from '../utils/config.js'; - export default class EmailVerify extends React.Component { constructor(props) { super(props); @@ -19,10 +17,10 @@ export default class EmailVerify extends React.Component { var body = ''; var resend = ''; if (this.props.isVerified === 'true') { - title = config.SiteName + ' Email Verified'; + title = global.window.config.SiteName + ' Email Verified'; body = <p>Your email has been verified! Click <a href={this.props.teamURL + '?email=' + this.props.userEmail}>here</a> to log in.</p>; } else { - title = config.SiteName + ' Email Not Verified'; + title = global.window.config.SiteName + ' Email Not Verified'; body = <p>Please verify your email address. Check your inbox for an email.</p>; resend = ( <button diff --git a/web/react/components/find_team.jsx b/web/react/components/find_team.jsx index 52988886c..eb2683a88 100644 --- a/web/react/components/find_team.jsx +++ b/web/react/components/find_team.jsx @@ -3,7 +3,6 @@ var utils = require('../utils/utils.jsx'); var client = require('../utils/client.jsx'); -import {strings} from '../utils/config.js'; export default class FindTeam extends React.Component { constructor(props) { @@ -51,8 +50,8 @@ export default class FindTeam extends React.Component { if (this.state.sent) { return ( <div> - <h4>{'Find Your ' + utils.toTitleCase(strings.Team)}</h4> - <p>{'An email was sent with links to any ' + strings.TeamPlural + ' to which you are a member.'}</p> + <h4>{'Find Your team'}</h4> + <p>{'An email was sent with links to any teams to which you are a member.'}</p> </div> ); } @@ -61,7 +60,7 @@ export default class FindTeam extends React.Component { <div> <h4>Find Your Team</h4> <form onSubmit={this.handleSubmit}> - <p>{'Get an email with links to any ' + strings.TeamPlural + ' to which you are a member.'}</p> + <p>{'Get an email with links to any teams to which you are a member.'}</p> <div className='form-group'> <label className='control-label'>Email</label> <div className={emailErrorClass}> diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx index 1f25ea0b7..5d8b13f00 100644 --- a/web/react/components/get_link_modal.jsx +++ b/web/react/components/get_link_modal.jsx @@ -2,7 +2,6 @@ // See License.txt for license information. var UserStore = require('../stores/user_store.jsx'); -import {strings} from '../utils/config.js'; export default class GetLinkModal extends React.Component { constructor(props) { @@ -76,9 +75,9 @@ export default class GetLinkModal extends React.Component { </div> <div className='modal-body'> <p> - Send {strings.Team + 'mates'} the link below for them to sign-up to this {strings.Team} site. + Send teammates the link below for them to sign-up to this team site. <br /><br /> - Be careful not to share this link publicly, since anyone with the link can join your {strings.Team}. + Be careful not to share this link publicly, since anyone with the link can join your team. </p> <textarea className='form-control no-resize' diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx index c1cfa7800..650a72516 100644 --- a/web/react/components/invite_member_modal.jsx +++ b/web/react/components/invite_member_modal.jsx @@ -2,11 +2,9 @@ // See License.txt for license information. var utils = require('../utils/utils.jsx'); -var ConfigStore = require('../stores/config_store.jsx'); var Client = require('../utils/client.jsx'); var UserStore = require('../stores/user_store.jsx'); var ConfirmModal = require('./confirm_modal.jsx'); -import {config} from '../utils/config.js'; export default class InviteMemberModal extends React.Component { constructor(props) { @@ -23,7 +21,7 @@ export default class InviteMemberModal extends React.Component { emailErrors: {}, firstNameErrors: {}, lastNameErrors: {}, - emailEnabled: !ConfigStore.getSettingAsBoolean('ByPassEmail', false) + emailEnabled: !global.window.config.ByPassEmail }; } @@ -79,23 +77,9 @@ export default class InviteMemberModal extends React.Component { emailErrors[index] = ''; } - if (config.AllowInviteNames) { - invite.firstName = React.findDOMNode(this.refs['first_name' + index]).value.trim(); - if (!invite.firstName && config.RequireInviteNames) { - firstNameErrors[index] = 'This is a required field'; - valid = false; - } else { - firstNameErrors[index] = ''; - } + invite.firstName = React.findDOMNode(this.refs['first_name' + index]).value.trim(); - invite.lastName = React.findDOMNode(this.refs['last_name' + index]).value.trim(); - if (!invite.lastName && config.RequireInviteNames) { - lastNameErrors[index] = 'This is a required field'; - valid = false; - } else { - lastNameErrors[index] = ''; - } - } + invite.lastName = React.findDOMNode(this.refs['last_name' + index]).value.trim(); invites.push(invite); } @@ -143,10 +127,8 @@ export default class InviteMemberModal extends React.Component { for (var i = 0; i < inviteIds.length; i++) { var index = inviteIds[i]; React.findDOMNode(this.refs['email' + index]).value = ''; - if (config.AllowInviteNames) { - React.findDOMNode(this.refs['first_name' + index]).value = ''; - React.findDOMNode(this.refs['last_name' + index]).value = ''; - } + React.findDOMNode(this.refs['first_name' + index]).value = ''; + React.findDOMNode(this.refs['last_name' + index]).value = ''; } this.setState({ @@ -210,44 +192,43 @@ export default class InviteMemberModal extends React.Component { } var nameFields = null; - if (config.AllowInviteNames) { - var firstNameClass = 'form-group'; - if (firstNameError) { - firstNameClass += ' has-error'; - } - var lastNameClass = 'form-group'; - if (lastNameError) { - lastNameClass += ' has-error'; - } - nameFields = (<div className='row--invite'> - <div className='col-sm-6'> - <div className={firstNameClass}> - <input - type='text' - className='form-control' - ref={'first_name' + index} - placeholder='First name' - maxLength='64' - disabled={!this.state.emailEnabled} - /> - {firstNameError} - </div> + + var firstNameClass = 'form-group'; + if (firstNameError) { + firstNameClass += ' has-error'; + } + var lastNameClass = 'form-group'; + if (lastNameError) { + lastNameClass += ' has-error'; + } + nameFields = (<div className='row--invite'> + <div className='col-sm-6'> + <div className={firstNameClass}> + <input + type='text' + className='form-control' + ref={'first_name' + index} + placeholder='First name' + maxLength='64' + disabled={!this.state.emailEnabled} + /> + {firstNameError} </div> - <div className='col-sm-6'> - <div className={lastNameClass}> - <input - type='text' - className='form-control' - ref={'last_name' + index} - placeholder='Last name' - maxLength='64' - disabled={!this.state.emailEnabled} - /> - {lastNameError} - </div> + </div> + <div className='col-sm-6'> + <div className={lastNameClass}> + <input + type='text' + className='form-control' + ref={'last_name' + index} + placeholder='Last name' + maxLength='64' + disabled={!this.state.emailEnabled} + /> + {lastNameError} </div> - </div>); - } + </div> + </div>); inviteSections[index] = ( <div key={'key' + index}> diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx index b20c62833..ffc07a4dd 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -6,7 +6,6 @@ const Client = require('../utils/client.jsx'); const UserStore = require('../stores/user_store.jsx'); const BrowserStore = require('../stores/browser_store.jsx'); const Constants = require('../utils/constants.jsx'); -import {config, strings} from '../utils/config.js'; export default class Login extends React.Component { constructor(props) { @@ -177,7 +176,7 @@ export default class Login extends React.Component { <div className='signup-team__container'> <h5 className='margin--less'>Sign in to:</h5> <h2 className='signup-team__name'>{teamDisplayName}</h2> - <h2 className='signup-team__subdomain'>on {config.SiteName}</h2> + <h2 className='signup-team__subdomain'>on {global.window.config.SiteName}</h2> <form onSubmit={this.handleSubmit}> <div className={'form-group' + errorClass}> {serverError} @@ -185,11 +184,11 @@ export default class Login extends React.Component { {loginMessage} {emailSignup} <div className='form-group margin--extra form-group--small'> - <span><a href='/find_team'>{'Find other ' + strings.TeamPlural}</a></span> + <span><a href='/find_team'>{'Find other teams'}</a></span> </div> {forgotPassword} <div className='margin--extra'> - <span>{'Want to create your own ' + strings.Team + '? '} + <span>{'Want to create your own team? '} <a href='/' className='signup-team-login' diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx index 99cdfa1ad..b7566cfb9 100644 --- a/web/react/components/navbar_dropdown.jsx +++ b/web/react/components/navbar_dropdown.jsx @@ -7,7 +7,6 @@ var UserStore = require('../stores/user_store.jsx'); var TeamStore = require('../stores/team_store.jsx'); var Constants = require('../utils/constants.jsx'); -import {config} from '../utils/config.js'; function getStateFromStores() { return {teams: UserStore.getTeams(), currentTeam: TeamStore.getCurrent()}; @@ -188,7 +187,7 @@ export default class NavbarDropdown extends React.Component { <li> <a target='_blank' - href={config.HelpLink} + href='/static/help/help.html' > Help </a> @@ -196,7 +195,7 @@ export default class NavbarDropdown extends React.Component { <li> <a target='_blank' - href={config.ReportProblemLink} + href='/static/help/report_problem.html' > Report a Problem </a> diff --git a/web/react/components/new_channel_modal.jsx b/web/react/components/new_channel_modal.jsx index 2bf645dc5..f3fb8da2a 100644 --- a/web/react/components/new_channel_modal.jsx +++ b/web/react/components/new_channel_modal.jsx @@ -127,7 +127,7 @@ export default class NewChannelModal extends React.Component { href='#' onClick={this.props.onChangeURLPressed} > - {'change this URL'} + {'Edit'} </a> {')'} </p> diff --git a/web/react/components/password_reset_form.jsx b/web/react/components/password_reset_form.jsx index 1b579efbc..dae582627 100644 --- a/web/react/components/password_reset_form.jsx +++ b/web/react/components/password_reset_form.jsx @@ -2,7 +2,6 @@ // See License.txt for license information. var client = require('../utils/client.jsx'); -import {config} from '../utils/config.js'; export default class PasswordResetForm extends React.Component { constructor(props) { @@ -62,7 +61,7 @@ export default class PasswordResetForm extends React.Component { <div className='signup-team__container'> <h3>Password Reset</h3> <form onSubmit={this.handlePasswordReset}> - <p>{'Enter a new password for your ' + this.props.teamDisplayName + ' ' + config.SiteName + ' account.'}</p> + <p>{'Enter a new password for your ' + this.props.teamDisplayName + ' ' + global.window.config.SiteName + ' account.'}</p> <div className={formClass}> <input type='password' diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx index fb9522afb..ec873dd00 100644 --- a/web/react/components/popover_list_members.jsx +++ b/web/react/components/popover_list_members.jsx @@ -25,7 +25,7 @@ export default class PopoverListMembers extends React.Component { $('#member_popover').popover({placement: 'bottom', trigger: 'click', html: true}); $('body').on('click', function onClick(e) { - if ($(e.target.parentNode.parentNode)[0] !== $('#member_popover')[0] && $(e.target).parents('.popover.in').length === 0) { + if (e.target.parentNode && $(e.target.parentNode.parentNode)[0] !== $('#member_popover')[0] && $(e.target).parents('.popover.in').length === 0) { $('#member_popover').popover('hide'); } }); diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index e6aa3f8df..faa5e5f0b 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -15,8 +15,6 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; -import {strings} from '../utils/config.js'; - export default class PostList extends React.Component { constructor(props) { super(props); @@ -347,7 +345,7 @@ export default class PostList extends React.Component { return ( <div className='channel-intro'> - <p className='channel-intro-text'>{'This is the start of your private message history with this ' + strings.Team + 'mate. Private messages and files shared here are not shown to people outside this area.'}</p> + <p className='channel-intro-text'>{'This is the start of your private message history with this teammate. Private messages and files shared here are not shown to people outside this area.'}</p> </div> ); } @@ -369,7 +367,7 @@ export default class PostList extends React.Component { <p className='channel-intro__content'> Welcome to {channel.display_name}! <br/><br/> - This is the first channel {strings.Team}mates see when they + This is the first channel teammates see when they <br/> sign up - use it for posting updates everyone needs to know. <br/><br/> diff --git a/web/react/components/register_app_modal.jsx b/web/react/components/register_app_modal.jsx new file mode 100644 index 000000000..3dd5c094e --- /dev/null +++ b/web/react/components/register_app_modal.jsx @@ -0,0 +1,249 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Client = require('../utils/client.jsx'); + +export default class RegisterAppModal extends React.Component { + constructor() { + super(); + + this.register = this.register.bind(this); + this.onHide = this.onHide.bind(this); + this.save = this.save.bind(this); + + this.state = {clientId: '', clientSecret: '', saved: false}; + } + componentDidMount() { + $(React.findDOMNode(this)).on('hide.bs.modal', this.onHide); + } + register() { + var state = this.state; + state.serverError = null; + + var app = {}; + + var name = this.refs.name.getDOMNode().value; + if (!name || name.length === 0) { + state.nameError = 'Application name must be filled in.'; + this.setState(state); + return; + } + state.nameError = null; + app.name = name; + + var homepage = this.refs.homepage.getDOMNode().value; + if (!homepage || homepage.length === 0) { + state.homepageError = 'Homepage must be filled in.'; + this.setState(state); + return; + } + state.homepageError = null; + app.homepage = homepage; + + var desc = this.refs.desc.getDOMNode().value; + app.description = desc; + + var rawCallbacks = this.refs.callback.getDOMNode().value.trim(); + if (!rawCallbacks || rawCallbacks.length === 0) { + state.callbackError = 'At least one callback URL must be filled in.'; + this.setState(state); + return; + } + state.callbackError = null; + app.callback_urls = rawCallbacks.split('\n'); + + Client.registerOAuthApp(app, + (data) => { + state.clientId = data.id; + state.clientSecret = data.client_secret; + this.setState(state); + }, + (err) => { + state.serverError = err.message; + this.setState(state); + } + ); + } + onHide(e) { + if (!this.state.saved && this.state.clientId !== '') { + e.preventDefault(); + return; + } + + this.setState({clientId: '', clientSecret: '', saved: false}); + } + save() { + this.setState({saved: this.refs.save.getDOMNode().checked}); + } + render() { + var nameError; + if (this.state.nameError) { + nameError = <div className='form-group has-error'><label className='control-label'>{this.state.nameError}</label></div>; + } + var homepageError; + if (this.state.homepageError) { + homepageError = <div className='form-group has-error'><label className='control-label'>{this.state.homepageError}</label></div>; + } + var callbackError; + if (this.state.callbackError) { + callbackError = <div className='form-group has-error'><label className='control-label'>{this.state.callbackError}</label></div>; + } + var serverError; + if (this.state.serverError) { + serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; + } + + var body = ''; + if (this.state.clientId === '') { + body = ( + <div className='form-group user-settings'> + <h3>{'Register a New Application'}</h3> + <br/> + <label className='col-sm-4 control-label'>{'Application Name'}</label> + <div className='col-sm-7'> + <input + ref='name' + className='form-control' + type='text' + placeholder='Required' + /> + {nameError} + </div> + <br/> + <br/> + <label className='col-sm-4 control-label'>{'Homepage URL'}</label> + <div className='col-sm-7'> + <input + ref='homepage' + className='form-control' + type='text' + placeholder='Required' + /> + {homepageError} + </div> + <br/> + <br/> + <label className='col-sm-4 control-label'>{'Description'}</label> + <div className='col-sm-7'> + <input + ref='desc' + className='form-control' + type='text' + placeholder='Optional' + /> + </div> + <br/> + <br/> + <label className='col-sm-4 control-label'>{'Callback URL'}</label> + <div className='col-sm-7'> + <textarea + ref='callback' + className='form-control' + type='text' + placeholder='Required' + rows='5' + /> + {callbackError} + </div> + <br/> + <br/> + <br/> + <br/> + <br/> + {serverError} + <a + className='btn btn-sm theme pull-right' + href='#' + data-dismiss='modal' + aria-label='Close' + > + {'Cancel'} + </a> + <a + className='btn btn-sm btn-primary pull-right' + onClick={this.register} + > + {'Register'} + </a> + </div> + ); + } else { + var btnClass = ' disabled'; + if (this.state.saved) { + btnClass = ''; + } + + body = ( + <div className='form-group user-settings'> + <h3>{'Your Application Credentials'}</h3> + <br/> + <br/> + <label className='col-sm-12 control-label'>{'Client ID: '}{this.state.clientId}</label> + <label className='col-sm-12 control-label'>{'Client Secret: '}{this.state.clientSecret}</label> + <br/> + <br/> + <br/> + <br/> + <strong>{'Save these somewhere SAFE and SECURE. We can retrieve your Client Id if you lose it, but your Client Secret will be lost forever if you were to lose it.'}</strong> + <br/> + <br/> + <div className='checkbox'> + <label> + <input + ref='save' + type='checkbox' + checked={this.state.saved} + onClick={this.save} + > + {'I have saved both my Client Id and Client Secret somewhere safe'} + </input> + </label> + </div> + <a + className={'btn btn-sm btn-primary pull-right' + btnClass} + href='#' + data-dismiss='modal' + aria-label='Close' + > + {'Close'} + </a> + </div> + ); + } + + return ( + <div + className='modal fade' + ref='modal' + id='register_app' + role='dialog' + aria-hidden='true' + > + <div className='modal-dialog'> + <div className='modal-content'> + <div className='modal-header'> + <button + type='button' + className='close' + data-dismiss='modal' + aria-label='Close' + > + <span aria-hidden='true'>{'x'}</span> + </button> + <h4 + className='modal-title' + ref='title' + > + {'Developer Applications'} + </h4> + </div> + <div className='modal-body'> + {body} + </div> + </div> + </div> + </div> + ); + } +} + diff --git a/web/react/components/search_bar.jsx b/web/react/components/search_bar.jsx index 006d15459..77166fef9 100644 --- a/web/react/components/search_bar.jsx +++ b/web/react/components/search_bar.jsx @@ -75,6 +75,9 @@ export default class SearchBar extends React.Component { PostStore.emitSearchTermChange(false); this.setState({searchTerm: term}); } + handleMouseInput(e) { + e.preventDefault(); + } handleUserFocus(e) { e.target.select(); $('.search-bar__container').addClass('focused'); @@ -140,6 +143,7 @@ export default class SearchBar extends React.Component { value={this.state.searchTerm} onFocus={this.handleUserFocus} onChange={this.handleUserInput} + onMouseUp={this.handleMouseInput} /> {isSearching} </form> diff --git a/web/react/components/setting_item_max.jsx b/web/react/components/setting_item_max.jsx index b1bab1d48..1bffa7c79 100644 --- a/web/react/components/setting_item_max.jsx +++ b/web/react/components/setting_item_max.jsx @@ -26,7 +26,7 @@ export default class SettingItemMax extends React.Component { href='#' onClick={this.props.submit} > - Submit + Save </a> ); } diff --git a/web/react/components/setting_picture.jsx b/web/react/components/setting_picture.jsx index a53112651..ddad4fd53 100644 --- a/web/react/components/setting_picture.jsx +++ b/web/react/components/setting_picture.jsx @@ -1,8 +1,6 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -import {config} from '../utils/config.js'; - export default class SettingPicture extends React.Component { constructor(props) { super(props); @@ -81,7 +79,7 @@ export default class SettingPicture extends React.Component { >Save</a> ); } - var helpText = 'Upload a profile picture in either JPG or PNG format, at least ' + config.ProfileWidth + 'px in width and ' + config.ProfileHeight + 'px height.'; + var helpText = 'Upload a profile picture in either JPG or PNG format, at least ' + global.window.config.ProfileWidth + 'px in width and ' + global.window.config.ProfileHeight + 'px height.'; var self = this; return ( diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 977fecb5c..87007edcc 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -13,6 +13,7 @@ var SidebarHeader = require('./sidebar_header.jsx'); var SearchBox = require('./search_bar.jsx'); var Constants = require('../utils/constants.jsx'); var NewChannelFlow = require('./new_channel_flow.jsx'); +var UnreadChannelIndicator = require('./unread_channel_indicator.jsx'); export default class Sidebar extends React.Component { constructor(props) { @@ -153,6 +154,16 @@ export default class Sidebar extends React.Component { $(window).on('resize', this.onResize); } + shouldComponentUpdate(nextProps, nextState) { + if (!Utils.areStatesEqual(nextProps, this.props)) { + return true; + } + + if (!Utils.areStatesEqual(nextState, this.state)) { + return true; + } + return false; + } componentDidUpdate() { this.updateTitle(); this.updateUnreadIndicators(); @@ -274,15 +285,16 @@ export default class Sidebar extends React.Component { this.updateUnreadIndicators(); } updateUnreadIndicators() { - var container = $(React.findDOMNode(this.refs.container)); + const container = $(React.findDOMNode(this.refs.container)); + + var showTopUnread = false; + var showBottomUnread = false; if (this.firstUnreadChannel) { var firstUnreadElement = $(React.findDOMNode(this.refs[this.firstUnreadChannel])); if (firstUnreadElement.position().top + firstUnreadElement.height() < 0) { - $(React.findDOMNode(this.refs.topUnreadIndicator)).css('display', 'initial'); - } else { - $(React.findDOMNode(this.refs.topUnreadIndicator)).css('display', 'none'); + showTopUnread = true; } } @@ -290,11 +302,14 @@ export default class Sidebar extends React.Component { var lastUnreadElement = $(React.findDOMNode(this.refs[this.lastUnreadChannel])); if (lastUnreadElement.position().top > container.height()) { - $(React.findDOMNode(this.refs.bottomUnreadIndicator)).css('display', 'initial'); - } else { - $(React.findDOMNode(this.refs.bottomUnreadIndicator)).css('display', 'none'); + showBottomUnread = true; } } + + this.setState({ + showTopUnread, + showBottomUnread + }); } createChannelElement(channel, index) { var members = this.state.members; @@ -348,6 +363,11 @@ export default class Sidebar extends React.Component { ); } + var badgeClass; + if (msgCount > 0) { + badgeClass = 'has-badge'; + } + // set up status icon for direct message channels var status = null; if (channel.type === 'D') { @@ -408,7 +428,7 @@ export default class Sidebar extends React.Component { className={linkClass} > <a - className={'sidebar-channel ' + titleClass} + className={'sidebar-channel ' + titleClass + ' ' + badgeClass} href={href} onClick={handleClick} > @@ -427,19 +447,13 @@ export default class Sidebar extends React.Component { this.lastUnreadChannel = null; // create elements for all 3 types of channels - var channelItems = this.state.channels.filter( - function filterPublicChannels(channel) { - return channel.type === 'O'; - } - ).map(this.createChannelElement); + const publicChannels = this.state.channels.filter((channel) => channel.type === 'O'); + const publicChannelItems = publicChannels.map(this.createChannelElement); - var privateChannelItems = this.state.channels.filter( - function filterPrivateChannels(channel) { - return channel.type === 'P'; - } - ).map(this.createChannelElement); + const privateChannels = this.state.channels.filter((channel) => channel.type === 'P'); + const privateChannelItems = privateChannels.map(this.createChannelElement); - var directMessageItems = this.state.showDirectChannels.map(this.createChannelElement); + const directMessageItems = this.state.showDirectChannels.map(this.createChannelElement); // update the favicon to show if there are any notifications var link = document.createElement('link'); @@ -493,20 +507,16 @@ export default class Sidebar extends React.Component { /> <SearchBox /> - <div - ref='topUnreadIndicator' - className='nav-pills__unread-indicator nav-pills__unread-indicator-top' - style={{display: 'none'}} - > - Unread post(s) above - </div> - <div - ref='bottomUnreadIndicator' - className='nav-pills__unread-indicator nav-pills__unread-indicator-bottom' - style={{display: 'none'}} - > - Unread post(s) below - </div> + <UnreadChannelIndicator + show={this.state.showTopUnread} + extraClass='nav-pills__unread-indicator-top' + text={'Unread post(s) above'} + /> + <UnreadChannelIndicator + show={this.state.showBottomUnread} + extraClass='nav-pills__unread-indicator-bottom' + text={'Unread post(s) below'} + /> <div ref='container' @@ -526,7 +536,7 @@ export default class Sidebar extends React.Component { </a> </h4> </li> - {channelItems} + {publicChannelItems} <li> <a href='#' diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx index 0056d7a2f..959411f1e 100644 --- a/web/react/components/sidebar_header.jsx +++ b/web/react/components/sidebar_header.jsx @@ -3,7 +3,6 @@ var NavbarDropdown = require('./navbar_dropdown.jsx'); var UserStore = require('../stores/user_store.jsx'); -import {config} from '../utils/config.js'; export default class SidebarHeader extends React.Component { constructor(props) { @@ -59,7 +58,7 @@ export default class SidebarHeader extends React.Component { } SidebarHeader.defaultProps = { - teamDisplayName: config.SiteName, + teamDisplayName: global.window.config.SiteName, teamType: '' }; SidebarHeader.propTypes = { diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx index bd10a6ef1..5ecd502ba 100644 --- a/web/react/components/sidebar_right_menu.jsx +++ b/web/react/components/sidebar_right_menu.jsx @@ -4,7 +4,6 @@ var UserStore = require('../stores/user_store.jsx'); var client = require('../utils/client.jsx'); var utils = require('../utils/utils.jsx'); -import {config} from '../utils/config.js'; export default class SidebarRightMenu extends React.Component { constructor(props) { @@ -75,8 +74,8 @@ export default class SidebarRightMenu extends React.Component { } var siteName = ''; - if (config.SiteName != null) { - siteName = config.SiteName; + if (global.window.config.SiteName != null) { + siteName = global.window.config.SiteName; } var teamDisplayName = siteName; if (this.props.teamDisplayName) { diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx index dc0d1d376..9c03c5c2f 100644 --- a/web/react/components/signup_team_complete.jsx +++ b/web/react/components/signup_team_complete.jsx @@ -4,7 +4,6 @@ var WelcomePage = require('./team_signup_welcome_page.jsx'); var TeamDisplayNamePage = require('./team_signup_display_name_page.jsx'); var TeamURLPage = require('./team_signup_url_page.jsx'); -var AllowedDomainsPage = require('./team_signup_allowed_domains_page.jsx'); var SendInivtesPage = require('./team_signup_send_invites_page.jsx'); var UsernamePage = require('./team_signup_username_page.jsx'); var PasswordPage = require('./team_signup_password_page.jsx'); @@ -70,15 +69,6 @@ export default class SignupTeamComplete extends React.Component { ); } - if (this.state.wizard === 'allowed_domains') { - return ( - <AllowedDomainsPage - state={this.state} - updateParent={this.updateParent} - /> - ); - } - if (this.state.wizard === 'send_invites') { return ( <SendInivtesPage diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index 6e71eae32..19c3b2d22 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -6,7 +6,6 @@ var client = require('../utils/client.jsx'); var UserStore = require('../stores/user_store.jsx'); var BrowserStore = require('../stores/browser_store.jsx'); var Constants = require('../utils/constants.jsx'); -import {config} from '../utils/config.js'; export default class SignupUserComplete extends React.Component { constructor(props) { @@ -136,7 +135,7 @@ export default class SignupUserComplete extends React.Component { // set up the email entry and hide it if an email was provided var yourEmailIs = ''; if (this.state.user.email) { - yourEmailIs = <span>Your email address is {this.state.user.email}. You'll use this address to sign in to {config.SiteName}.</span>; + yourEmailIs = <span>Your email address is {this.state.user.email}. You'll use this address to sign in to {global.window.config.SiteName}.</span>; } var emailContainerStyle = 'margin--extra'; @@ -237,11 +236,6 @@ export default class SignupUserComplete extends React.Component { ); } - var termsDisclaimer = null; - if (config.ShowTermsDuringSignup) { - termsDisclaimer = <p>By creating an account and using Mattermost you are agreeing to our <a href={config.TermsLink}>Terms of Service</a>. If you do not agree, you cannot use this service.</p>; - } - return ( <div> <form> @@ -251,12 +245,11 @@ export default class SignupUserComplete extends React.Component { /> <h5 className='margin--less'>Welcome to:</h5> <h2 className='signup-team__name'>{this.props.teamDisplayName}</h2> - <h2 className='signup-team__subdomain'>on {config.SiteName}</h2> + <h2 className='signup-team__subdomain'>on {global.window.config.SiteName}</h2> <h4 className='color--light'>Let's create your account</h4> {signupMessage} {emailSignup} {serverError} - {termsDisclaimer} </form> </div> ); diff --git a/web/react/components/team_general_tab.jsx b/web/react/components/team_general_tab.jsx index 25139bb95..ca438df78 100644 --- a/web/react/components/team_general_tab.jsx +++ b/web/react/components/team_general_tab.jsx @@ -6,7 +6,6 @@ const SettingItemMax = require('./setting_item_max.jsx'); const Client = require('../utils/client.jsx'); const Utils = require('../utils/utils.jsx'); -import {strings} from '../utils/config.js'; export default class GeneralTab extends React.Component { constructor(props) { @@ -30,7 +29,7 @@ export default class GeneralTab extends React.Component { state.clientError = 'This field is required'; valid = false; } else if (name === this.props.teamDisplayName) { - state.clientError = 'Please choose a new name for your ' + strings.Team; + state.clientError = 'Please choose a new name for your team'; valid = false; } else { state.clientError = ''; @@ -99,7 +98,7 @@ export default class GeneralTab extends React.Component { if (this.props.activeSection === 'name') { let inputs = []; - let teamNameLabel = Utils.toTitleCase(strings.Team) + ' Name'; + let teamNameLabel = 'Team Name'; if (Utils.isMobile()) { teamNameLabel = ''; } @@ -123,7 +122,7 @@ export default class GeneralTab extends React.Component { nameSection = ( <SettingItemMax - title={`${Utils.toTitleCase(strings.Team)} Name`} + title={`Team Name`} inputs={inputs} submit={this.handleNameSubmit} server_error={serverError} @@ -136,7 +135,7 @@ export default class GeneralTab extends React.Component { nameSection = ( <SettingItemMin - title={`${Utils.toTitleCase(strings.Team)} Name`} + title={`Team Name`} describe={describe} updateSection={this.onUpdateSection} /> diff --git a/web/react/components/team_signup_allowed_domains_page.jsx b/web/react/components/team_signup_allowed_domains_page.jsx deleted file mode 100644 index 721fa142a..000000000 --- a/web/react/components/team_signup_allowed_domains_page.jsx +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. -// See License.txt for license information. - -var Client = require('../utils/client.jsx'); -import {strings} from '../utils/config.js'; - -export default class TeamSignupAllowedDomainsPage extends React.Component { - constructor(props) { - super(props); - - this.submitBack = this.submitBack.bind(this); - this.submitNext = this.submitNext.bind(this); - - this.state = {}; - } - submitBack(e) { - e.preventDefault(); - this.props.state.wizard = 'team_url'; - this.props.updateParent(this.props.state); - } - submitNext(e) { - e.preventDefault(); - - if (React.findDOMNode(this.refs.open_network).checked) { - this.props.state.wizard = 'send_invites'; - this.props.state.team.type = 'O'; - this.props.updateParent(this.props.state); - return; - } - - if (React.findDOMNode(this.refs.allow).checked) { - var name = React.findDOMNode(this.refs.name).value.trim(); - var domainRegex = /^\w+\.\w+$/; - if (!name) { - this.setState({nameError: 'This field is required'}); - return; - } - - if (!name.trim().match(domainRegex)) { - this.setState({nameError: 'The domain doesn\'t appear valid'}); - return; - } - - this.props.state.wizard = 'send_invites'; - this.props.state.team.allowed_domains = name; - this.props.state.team.type = 'I'; - this.props.updateParent(this.props.state); - } else { - this.props.state.wizard = 'send_invites'; - this.props.state.team.type = 'I'; - this.props.updateParent(this.props.state); - } - } - render() { - Client.track('signup', 'signup_team_04_allow_domains'); - - var nameError = null; - var nameDivClass = 'form-group'; - if (this.state.nameError) { - nameError = <label className='control-label'>{this.state.nameError}</label>; - nameDivClass += ' has-error'; - } - - return ( - <div> - <form> - <img - className='signup-team-logo' - src='/static/images/logo.png' - /> - <h2>Email Domain</h2> - <p> - <div className='checkbox'> - <label> - <input - type='checkbox' - ref='allow' - defaultChecked={true} - /> - {' Allow sign up and ' + strings.Team + ' discovery with a ' + strings.Company + ' email address.'} - </label> - </div> - </p> - <p>{'Check this box to allow your ' + strings.Team + ' members to sign up using their ' + strings.Company + ' email addresses if you share the same domain--otherwise, you need to invite everyone yourself.'}</p> - <h4>{'Your ' + strings.Team + '\'s domain for emails'}</h4> - <div className={nameDivClass}> - <div className='row'> - <div className='col-sm-9'> - <div className='input-group'> - <span className='input-group-addon'>@</span> - <input - type='text' - ref='name' - className='form-control' - placeholder='' - maxLength='128' - defaultValue={this.props.state.team.allowed_domains} - autoFocus={true} - onFocus={this.handleFocus} - /> - </div> - </div> - </div> - {nameError} - </div> - <p>To allow signups from multiple domains, separate each with a comma.</p> - <p> - <div className='checkbox'> - <label> - <input - type='checkbox' - ref='open_network' - defaultChecked={this.props.state.team.type === 'O'} - /> Allow anyone to signup to this domain without an invitation.</label> - </div> - </p> - <button - type='button' - className='btn btn-default' - onClick={this.submitBack} - > - <i className='glyphicon glyphicon-chevron-left'></i> Back - </button> - <button - type='submit' - className='btn-primary btn' - onClick={this.submitNext} - > - Next<i className='glyphicon glyphicon-chevron-right'></i> - </button> - </form> - </div> - ); - } -} - -TeamSignupAllowedDomainsPage.defaultProps = { - state: {} -}; -TeamSignupAllowedDomainsPage.propTypes = { - state: React.PropTypes.object, - updateParent: React.PropTypes.func -}; diff --git a/web/react/components/team_signup_choose_auth.jsx b/web/react/components/team_signup_choose_auth.jsx index acce6ab49..d3107c5c7 100644 --- a/web/react/components/team_signup_choose_auth.jsx +++ b/web/react/components/team_signup_choose_auth.jsx @@ -2,7 +2,6 @@ // See License.txt for license information. var Constants = require('../utils/constants.jsx'); -import {strings} from '../utils/config.js'; export default class ChooseAuthPage extends React.Component { constructor(props) { @@ -24,7 +23,7 @@ export default class ChooseAuthPage extends React.Component { } > <span className='icon' /> - <span>Create new {strings.Team} with GitLab Account</span> + <span>Create new team with GitLab Account</span> </a> ); } @@ -42,7 +41,7 @@ export default class ChooseAuthPage extends React.Component { } > <span className='fa fa-envelope' /> - <span>Create new {strings.Team} with email address</span> + <span>Create new team with email address</span> </a> ); } @@ -55,7 +54,7 @@ export default class ChooseAuthPage extends React.Component { <div> {buttons} <div className='form-group margin--extra-2x'> - <span><a href='/find_team'>{'Find my ' + strings.Team}</a></span> + <span><a href='/find_team'>{'Find my team'}</a></span> </div> </div> ); diff --git a/web/react/components/team_signup_display_name_page.jsx b/web/react/components/team_signup_display_name_page.jsx index 1849f8222..c0d0ed366 100644 --- a/web/react/components/team_signup_display_name_page.jsx +++ b/web/react/components/team_signup_display_name_page.jsx @@ -3,7 +3,6 @@ var utils = require('../utils/utils.jsx'); var client = require('../utils/client.jsx'); -import {strings} from '../utils/config.js'; export default class TeamSignupDisplayNamePage extends React.Component { constructor(props) { @@ -54,7 +53,7 @@ export default class TeamSignupDisplayNamePage extends React.Component { className='signup-team-logo' src='/static/images/logo.png' /> - <h2>{utils.toTitleCase(strings.Team) + ' Name'}</h2> + <h2>{'Team Name'}</h2> <div className={nameDivClass}> <div className='row'> <div className='col-sm-9'> @@ -73,7 +72,7 @@ export default class TeamSignupDisplayNamePage extends React.Component { {nameError} </div> <div> - {'Name your ' + strings.Team + ' in any language. Your ' + strings.Team + ' name shows in menus and headings.'} + {'Name your team in any language. Your team name shows in menus and headings.'} </div> <button type='submit' diff --git a/web/react/components/team_signup_password_page.jsx b/web/react/components/team_signup_password_page.jsx index aa402846b..b26d9f6ce 100644 --- a/web/react/components/team_signup_password_page.jsx +++ b/web/react/components/team_signup_password_page.jsx @@ -4,7 +4,6 @@ var Client = require('../utils/client.jsx'); var BrowserStore = require('../stores/browser_store.jsx'); var UserStore = require('../stores/user_store.jsx'); -import {strings, config} from '../utils/config.js'; export default class TeamSignupPasswordPage extends React.Component { constructor(props) { @@ -123,13 +122,13 @@ export default class TeamSignupPasswordPage extends React.Component { type='submit' className='btn btn-primary margin--extra' id='finish-button' - data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Creating ' + strings.Team + '...'} + data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Creating team...'} onClick={this.submitNext} > Finish </button> </div> - <p>By proceeding to create your account and use {config.SiteName}, you agree to our <a href={config.TermsLink}>Terms of Service</a> and <a href={config.PrivacyLink}>Privacy Policy</a>. If you do not agree, you cannot use {config.SiteName}.</p> + <p>By proceeding to create your account and use {global.window.config.SiteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {global.window.config.SiteName}.</p> <div className='margin--extra'> <a href='#' diff --git a/web/react/components/team_signup_send_invites_page.jsx b/web/react/components/team_signup_send_invites_page.jsx index 11a9980d7..41ac98303 100644 --- a/web/react/components/team_signup_send_invites_page.jsx +++ b/web/react/components/team_signup_send_invites_page.jsx @@ -2,10 +2,7 @@ // See License.txt for license information. var EmailItem = require('./team_signup_email_item.jsx'); -var Utils = require('../utils/utils.jsx'); -var ConfigStore = require('../stores/config_store.jsx'); var Client = require('../utils/client.jsx'); -import {strings, config} from '../utils/config.js'; export default class TeamSignupSendInvitesPage extends React.Component { constructor(props) { @@ -16,7 +13,7 @@ export default class TeamSignupSendInvitesPage extends React.Component { this.submitSkip = this.submitSkip.bind(this); this.keySubmit = this.keySubmit.bind(this); this.state = { - emailEnabled: !ConfigStore.getSettingAsBoolean('ByPassEmail', false) + emailEnabled: !global.window.config.ByPassEmail }; if (!this.state.emailEnabled) { @@ -26,12 +23,7 @@ export default class TeamSignupSendInvitesPage extends React.Component { } submitBack(e) { e.preventDefault(); - - if (config.AllowSignupDomainsWizard) { - this.props.state.wizard = 'allowed_domains'; - } else { - this.props.state.wizard = 'team_url'; - } + this.props.state.wizard = 'team_url'; this.props.updateParent(this.props.state); } @@ -138,7 +130,7 @@ export default class TeamSignupSendInvitesPage extends React.Component { bottomContent = ( <p className='color--light'> - {'if you prefer, you can invite ' + strings.Team + ' members later'} + {'if you prefer, you can invite team members later'} <br /> {' and '} <a @@ -153,7 +145,7 @@ export default class TeamSignupSendInvitesPage extends React.Component { } else { content = ( <div className='form-group color--light'> - {'Email is currently disabled for your ' + strings.Team + ', and emails cannot be sent. Contact your system administrator to enable email and email invitations.'} + {'Email is currently disabled for your team, and emails cannot be sent. Contact your system administrator to enable email and email invitations.'} </div> ); } @@ -165,7 +157,7 @@ export default class TeamSignupSendInvitesPage extends React.Component { className='signup-team-logo' src='/static/images/logo.png' /> - <h2>{'Invite ' + Utils.toTitleCase(strings.Team) + ' Members'}</h2> + <h2>{'Invite Team Members'}</h2> {content} <div className='form-group'> <button diff --git a/web/react/components/team_signup_url_page.jsx b/web/react/components/team_signup_url_page.jsx index ffe9d9fe8..1b722d611 100644 --- a/web/react/components/team_signup_url_page.jsx +++ b/web/react/components/team_signup_url_page.jsx @@ -4,7 +4,6 @@ const Utils = require('../utils/utils.jsx'); const Client = require('../utils/client.jsx'); const Constants = require('../utils/constants.jsx'); -import {strings, config} from '../utils/config.js'; export default class TeamSignupUrlPage extends React.Component { constructor(props) { @@ -51,12 +50,8 @@ export default class TeamSignupUrlPage extends React.Component { Client.findTeamByName(name, function success(data) { if (!data) { - if (config.AllowSignupDomainsWizard) { - this.props.state.wizard = 'allowed_domains'; - } else { - this.props.state.wizard = 'send_invites'; - this.props.state.team.type = 'O'; - } + this.props.state.wizard = 'send_invites'; + this.props.state.team.type = 'O'; this.props.state.team.name = name; this.props.updateParent(this.props.state); @@ -97,7 +92,7 @@ export default class TeamSignupUrlPage extends React.Component { className='signup-team-logo' src='/static/images/logo.png' /> - <h2>{`${Utils.toTitleCase(strings.Team)} URL`}</h2> + <h2>{`Team URL`}</h2> <div className={nameDivClass}> <div className='row'> <div className='col-sm-11'> @@ -124,7 +119,7 @@ export default class TeamSignupUrlPage extends React.Component { </div> {nameError} </div> - <p>{`Choose the web address of your new ${strings.Team}:`}</p> + <p>{`Choose the web address of your new team:`}</p> <ul className='color--light'> <li>Short and memorable is best</li> <li>Use lowercase letters, numbers and dashes</li> diff --git a/web/react/components/team_signup_username_page.jsx b/web/react/components/team_signup_username_page.jsx index 984c7afab..0053b011d 100644 --- a/web/react/components/team_signup_username_page.jsx +++ b/web/react/components/team_signup_username_page.jsx @@ -3,7 +3,6 @@ var Utils = require('../utils/utils.jsx'); var Client = require('../utils/client.jsx'); -import {strings} from '../utils/config.js'; export default class TeamSignupUsernamePage extends React.Component { constructor(props) { @@ -55,7 +54,7 @@ export default class TeamSignupUsernamePage extends React.Component { src='/static/images/logo.png' /> <h2 className='margin--less'>Your username</h2> - <h5 className='color--light'>{'Select a memorable username that makes it easy for ' + strings.Team + 'mates to identify you:'}</h5> + <h5 className='color--light'>{'Select a memorable username that makes it easy for teammates to identify you:'}</h5> <div className='inner__content margin--extra'> <div className={nameDivClass}> <div className='row'> diff --git a/web/react/components/team_signup_welcome_page.jsx b/web/react/components/team_signup_welcome_page.jsx index 43b7aea0e..626c6a17b 100644 --- a/web/react/components/team_signup_welcome_page.jsx +++ b/web/react/components/team_signup_welcome_page.jsx @@ -4,7 +4,6 @@ var Utils = require('../utils/utils.jsx'); var Client = require('../utils/client.jsx'); var BrowserStore = require('../stores/browser_store.jsx'); -import {config} from '../utils/config.js'; export default class TeamSignupWelcomePage extends React.Component { constructor(props) { @@ -112,7 +111,7 @@ export default class TeamSignupWelcomePage extends React.Component { src='/static/images/logo.png' /> <h3 className='sub-heading'>Welcome to:</h3> - <h1 className='margin--top-none'>{config.SiteName}</h1> + <h1 className='margin--top-none'>{global.window.config.SiteName}</h1> </p> <p className='margin--less'>Let's set up your new team</p> <p> diff --git a/web/react/components/team_signup_with_email.jsx b/web/react/components/team_signup_with_email.jsx index d75736bd3..4fb1c0d01 100644 --- a/web/react/components/team_signup_with_email.jsx +++ b/web/react/components/team_signup_with_email.jsx @@ -3,7 +3,6 @@ const Utils = require('../utils/utils.jsx'); const Client = require('../utils/client.jsx'); -import {strings} from '../utils/config.js'; export default class EmailSignUpPage extends React.Component { constructor() { @@ -70,7 +69,7 @@ export default class EmailSignUpPage extends React.Component { </button> </div> <div className='form-group margin--extra-2x'> - <span><a href='/find_team'>{`Find my ${strings.Team}`}</a></span> + <span><a href='/find_team'>{`Find my team`}</a></span> </div> </form> ); diff --git a/web/react/components/team_signup_with_sso.jsx b/web/react/components/team_signup_with_sso.jsx index 521c21733..2849b4cbb 100644 --- a/web/react/components/team_signup_with_sso.jsx +++ b/web/react/components/team_signup_with_sso.jsx @@ -4,7 +4,6 @@ var utils = require('../utils/utils.jsx'); var client = require('../utils/client.jsx'); var Constants = require('../utils/constants.jsx'); -import {strings} from '../utils/config.js'; export default class SSOSignUpPage extends React.Component { constructor(props) { @@ -84,7 +83,7 @@ export default class SSOSignUpPage extends React.Component { disabled={disabled} > <span className='icon'/> - <span>Create {strings.Team} with GitLab Account</span> + <span>Create team with GitLab Account</span> </a> ); } @@ -111,7 +110,7 @@ export default class SSOSignUpPage extends React.Component { {serverError} </div> <div className='form-group margin--extra-2x'> - <span><a href='/find_team'>{'Find my ' + strings.Team}</a></span> + <span><a href='/find_team'>{'Find my team'}</a></span> </div> </form> ); diff --git a/web/react/components/unread_channel_indicator.jsx b/web/react/components/unread_channel_indicator.jsx new file mode 100644 index 000000000..12a67633e --- /dev/null +++ b/web/react/components/unread_channel_indicator.jsx @@ -0,0 +1,35 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +// Indicator for the left sidebar which indicate if there's unread posts in a channel that is not shown +// because it is either above or below the screen +export default class UnreadChannelIndicator extends React.Component { + constructor(props) { + super(props); + } + render() { + let displayValue = 'none'; + if (this.props.show) { + displayValue = 'initial'; + } + return ( + <div + className={'nav-pills__unread-indicator ' + this.props.extraClass} + style={{display: displayValue}} + > + {this.props.text} + </div> + ); + } +} + +UnreadChannelIndicator.defaultProps = { + show: false, + extraClass: '', + text: '' +}; +UnreadChannelIndicator.propTypes = { + show: React.PropTypes.bool, + extraClass: React.PropTypes.string, + text: React.PropTypes.string +}; diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx index 739084053..7cfac69e7 100644 --- a/web/react/components/user_profile.jsx +++ b/web/react/components/user_profile.jsx @@ -3,7 +3,6 @@ var Utils = require('../utils/utils.jsx'); var UserStore = require('../stores/user_store.jsx'); -import {config} from '../utils/config.js'; var id = 0; @@ -58,7 +57,7 @@ export default class UserProfile extends React.Component { } var dataContent = '<img class="user-popover__image" src="/api/v1/users/' + this.state.profile.id + '/image?time=' + this.state.profile.update_at + '" height="128" width="128" />'; - if (!config.ShowEmail) { + if (!global.window.config.ShowEmailAddress) { dataContent += '<div class="text-nowrap">Email not shared</div>'; } else { dataContent += '<div data-toggle="tooltip" title="' + this.state.profile.email + '"><a href="mailto:' + this.state.profile.email + '" class="text-nowrap text-lowercase user-popover__email">' + this.state.profile.email + '</a></div>'; diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx index 2a607b3e0..48b499068 100644 --- a/web/react/components/user_settings.jsx +++ b/web/react/components/user_settings.jsx @@ -7,6 +7,7 @@ var NotificationsTab = require('./user_settings_notifications.jsx'); var SecurityTab = require('./user_settings_security.jsx'); var GeneralTab = require('./user_settings_general.jsx'); var AppearanceTab = require('./user_settings_appearance.jsx'); +var DeveloperTab = require('./user_settings_developer.jsx'); export default class UserSettings extends React.Component { constructor(props) { @@ -76,6 +77,15 @@ export default class UserSettings extends React.Component { /> </div> ); + } else if (this.props.activeTab === 'developer') { + return ( + <div> + <DeveloperTab + activeSection={this.props.activeSection} + updateSection={this.props.updateSection} + /> + </div> + ); } return <div/>; diff --git a/web/react/components/user_settings_appearance.jsx b/web/react/components/user_settings_appearance.jsx index 3afdd7349..3df013d03 100644 --- a/web/react/components/user_settings_appearance.jsx +++ b/web/react/components/user_settings_appearance.jsx @@ -6,7 +6,8 @@ var SettingItemMin = require('./setting_item_min.jsx'); var SettingItemMax = require('./setting_item_max.jsx'); var Client = require('../utils/client.jsx'); var Utils = require('../utils/utils.jsx'); -import {config} from '../utils/config.js'; + +var ThemeColors = ['#2389d7', '#008a17', '#dc4fad', '#ac193d', '#0072c6', '#d24726', '#ff8f32', '#82ba00', '#03b3b2', '#008299', '#4617b4', '#8c0095', '#004b8b', '#004b8b', '#570000', '#380000', '#585858', '#000000']; export default class UserSettingsAppearance extends React.Component { constructor(props) { @@ -21,8 +22,8 @@ export default class UserSettingsAppearance extends React.Component { getStateFromStores() { var user = UserStore.getCurrentUser(); var theme = '#2389d7'; - if (config.ThemeColors != null) { - theme = config.ThemeColors[0]; + if (ThemeColors != null) { + theme = ThemeColors[0]; } if (user.props && user.props.theme) { theme = user.props.theme; @@ -83,18 +84,18 @@ export default class UserSettingsAppearance extends React.Component { var themeSection; var self = this; - if (config.ThemeColors != null) { + if (ThemeColors != null) { if (this.props.activeSection === 'theme') { var themeButtons = []; - for (var i = 0; i < config.ThemeColors.length; i++) { + for (var i = 0; i < ThemeColors.length; i++) { themeButtons.push( <button - key={config.ThemeColors[i] + 'key' + i} - ref={config.ThemeColors[i]} + key={ThemeColors[i] + 'key' + i} + ref={ThemeColors[i]} type='button' className='btn btn-lg color-btn' - style={{backgroundColor: config.ThemeColors[i]}} + style={{backgroundColor: ThemeColors[i]}} onClick={this.updateTheme} /> ); diff --git a/web/react/components/user_settings_developer.jsx b/web/react/components/user_settings_developer.jsx new file mode 100644 index 000000000..1b04149dc --- /dev/null +++ b/web/react/components/user_settings_developer.jsx @@ -0,0 +1,93 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var SettingItemMin = require('./setting_item_min.jsx'); +var SettingItemMax = require('./setting_item_max.jsx'); + +export default class DeveloperTab extends React.Component { + constructor(props) { + super(props); + + this.state = {}; + } + register() { + $('#user_settings1').modal('hide'); + $('#register_app').modal('show'); + } + render() { + var appSection; + var self = this; + if (this.props.activeSection === 'app') { + var inputs = []; + + inputs.push( + <div className='form-group'> + <div className='col-sm-7'> + <a + className='btn btn-sm btn-primary' + onClick={this.register} + > + {'Register New Application'} + </a> + </div> + </div> + ); + + appSection = ( + <SettingItemMax + title='Applications (Preview)' + inputs={inputs} + updateSection={function updateSection(e) { + self.props.updateSection(''); + e.preventDefault(); + }} + /> + ); + } else { + appSection = ( + <SettingItemMin + title='Applications (Preview)' + describe='Open to register a new third-party application' + updateSection={function updateSection() { + self.props.updateSection('app'); + }} + /> + ); + } + + return ( + <div> + <div className='modal-header'> + <button + type='button' + className='close' + data-dismiss='modal' + aria-label='Close' + > + <span aria-hidden='true'>{'x'}</span> + </button> + <h4 + className='modal-title' + ref='title' + > + <i className='modal-back'></i>{'Developer Settings'} + </h4> + </div> + <div className='user-settings'> + <h3 className='tab-header'>{'Developer Settings'}</h3> + <div className='divider-dark first'/> + {appSection} + <div className='divider-dark'/> + </div> + </div> + ); + } +} + +DeveloperTab.defaultProps = { + activeSection: '' +}; +DeveloperTab.propTypes = { + activeSection: React.PropTypes.string, + updateSection: React.PropTypes.func +}; diff --git a/web/react/components/user_settings_general.jsx b/web/react/components/user_settings_general.jsx index dd0abc8a5..66cde6ca2 100644 --- a/web/react/components/user_settings_general.jsx +++ b/web/react/components/user_settings_general.jsx @@ -2,7 +2,6 @@ // See License.txt for license information. var UserStore = require('../stores/user_store.jsx'); -var ConfigStore = require('../stores/config_store.jsx'); var SettingItemMin = require('./setting_item_min.jsx'); var SettingItemMax = require('./setting_item_max.jsx'); var SettingPicture = require('./setting_picture.jsx'); @@ -209,7 +208,7 @@ export default class UserSettingsGeneralTab extends React.Component { } setupInitialState(props) { var user = props.user; - var emailEnabled = !ConfigStore.getSettingAsBoolean('ByPassEmail', false); + var emailEnabled = !global.window.config.ByPassEmail; return {username: user.username, firstName: user.first_name, lastName: user.last_name, nickname: user.nickname, email: user.email, picture: null, loadingPicture: false, emailEnabled: emailEnabled}; } diff --git a/web/react/components/user_settings_modal.jsx b/web/react/components/user_settings_modal.jsx index 7ec75e000..67a4d0041 100644 --- a/web/react/components/user_settings_modal.jsx +++ b/web/react/components/user_settings_modal.jsx @@ -17,8 +17,8 @@ export default class UserSettingsModal extends React.Component { $('body').on('click', '.modal-back', function changeDisplay() { $(this).closest('.modal-dialog').removeClass('display--content'); }); - $('body').on('click', '.modal-header .close', function closeModal() { - setTimeout(function finishClose() { + $('body').on('click', '.modal-header .close', () => { + setTimeout(() => { $('.modal-dialog.display--content').removeClass('display--content'); }, 500); }); @@ -35,6 +35,9 @@ export default class UserSettingsModal extends React.Component { tabs.push({name: 'security', uiName: 'Security', icon: 'glyphicon glyphicon-lock'}); tabs.push({name: 'notifications', uiName: 'Notifications', icon: 'glyphicon glyphicon-exclamation-sign'}); tabs.push({name: 'appearance', uiName: 'Appearance', icon: 'glyphicon glyphicon-wrench'}); + if (global.window.config.EnableOAuthServiceProvider === 'true') { + tabs.push({name: 'developer', uiName: 'Developer', icon: 'glyphicon glyphicon-th'}); + } return ( <div @@ -54,13 +57,13 @@ export default class UserSettingsModal extends React.Component { data-dismiss='modal' aria-label='Close' > - <span aria-hidden='true'>×</span> + <span aria-hidden='true'>{'x'}</span> </button> <h4 className='modal-title' ref='title' > - Account Settings + {'Account Settings'} </h4> </div> <div className='modal-body'> diff --git a/web/react/components/user_settings_notifications.jsx b/web/react/components/user_settings_notifications.jsx index 33db1a332..dadbb669b 100644 --- a/web/react/components/user_settings_notifications.jsx +++ b/web/react/components/user_settings_notifications.jsx @@ -8,7 +8,6 @@ var client = require('../utils/client.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var utils = require('../utils/utils.jsx'); var assign = require('object-assign'); -import {config} from '../utils/config.js'; function getNotificationsStateFromStores() { var user = UserStore.getCurrentUser(); @@ -415,7 +414,7 @@ export default class NotificationsTab extends React.Component { </label> <br/> </div> - <div><br/>{'Email notifications are sent for mentions and private messages after you have been away from ' + config.SiteName + ' for 5 minutes.'}</div> + <div><br/>{'Email notifications are sent for mentions and private messages after you have been away from ' + global.window.config.SiteName + ' for 5 minutes.'}</div> </div> ); diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx index 8d3495e3b..f7c980396 100644 --- a/web/react/components/view_image.jsx +++ b/web/react/components/view_image.jsx @@ -3,7 +3,6 @@ var Client = require('../utils/client.jsx'); var Utils = require('../utils/utils.jsx'); -import {config} from '../utils/config.js'; export default class ViewImageModal extends React.Component { constructor(props) { @@ -301,7 +300,7 @@ export default class ViewImageModal extends React.Component { } var publicLink = ''; - if (config.AllowPublicLink) { + if (global.window.config.AllowPublicLink) { publicLink = ( <div> <a diff --git a/web/react/package.json b/web/react/package.json index 11d60376d..04e0f6bab 100644 --- a/web/react/package.json +++ b/web/react/package.json @@ -8,7 +8,8 @@ "keymirror": "0.1.1", "object-assign": "3.0.0", "react-zeroclipboard-mixin": "0.1.0", - "twemoji": "1.4.1" + "twemoji": "1.4.1", + "babel-runtime": "5.8.24" }, "devDependencies": { "browserify": "11.0.1", @@ -26,8 +27,8 @@ }, "browserify": { "transform": [ - "babelify", - "envify" + ["babelify", { "optional": ["runtime"] }], + "envify" ] }, "jest": { diff --git a/web/react/pages/authorize.jsx b/web/react/pages/authorize.jsx new file mode 100644 index 000000000..db42c8266 --- /dev/null +++ b/web/react/pages/authorize.jsx @@ -0,0 +1,21 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Authorize = require('../components/authorize.jsx'); + +function setupAuthorizePage(teamName, appName, responseType, clientId, redirectUri, scope, state) { + React.render( + <Authorize + teamName={teamName} + appName={appName} + responseType={responseType} + clientId={clientId} + redirectUri={redirectUri} + scope={scope} + state={state} + />, + document.getElementById('authorize') + ); +} + +global.window.setup_authorize_page = setupAuthorizePage; diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx index e70b51865..43493de45 100644 --- a/web/react/pages/channel.jsx +++ b/web/react/pages/channel.jsx @@ -33,24 +33,21 @@ var AccessHistoryModal = require('../components/access_history_modal.jsx'); var ActivityLogModal = require('../components/activity_log_modal.jsx'); var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx'); var FileUploadOverlay = require('../components/file_upload_overlay.jsx'); - -var AsyncClient = require('../utils/async_client.jsx'); +var RegisterAppModal = require('../components/register_app_modal.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; -function setupChannelPage(teamName, teamType, teamId, channelName, channelId) { - AsyncClient.getConfig(); - +function setupChannelPage(props) { AppDispatcher.handleViewAction({ type: ActionTypes.CLICK_CHANNEL, - name: channelName, - id: channelId + name: props.ChannelName, + id: props.ChannelId }); AppDispatcher.handleViewAction({ type: ActionTypes.CLICK_TEAM, - id: teamId + id: props.TeamId }); // ChannelLoader must be rendered first @@ -65,14 +62,14 @@ function setupChannelPage(teamName, teamType, teamId, channelName, channelId) { ); React.render( - <Navbar teamDisplayName={teamName} />, + <Navbar teamDisplayName={props.TeamDisplayName} />, document.getElementById('navbar') ); React.render( <Sidebar - teamDisplayName={teamName} - teamType={teamType} + teamDisplayName={props.TeamDisplayName} + teamType={props.TeamType} />, document.getElementById('sidebar-left') ); @@ -88,17 +85,17 @@ function setupChannelPage(teamName, teamType, teamId, channelName, channelId) { ); React.render( - <TeamSettingsModal teamDisplayName={teamName} />, + <TeamSettingsModal teamDisplayName={props.TeamDisplayName} />, document.getElementById('team_settings_modal') ); React.render( - <TeamMembersModal teamDisplayName={teamName} />, + <TeamMembersModal teamDisplayName={props.TeamDisplayName} />, document.getElementById('team_members_modal') ); React.render( - <MemberInviteModal teamType={teamType} />, + <MemberInviteModal teamType={props.TeamType} />, document.getElementById('invite_member_modal') ); @@ -184,8 +181,8 @@ function setupChannelPage(teamName, teamType, teamId, channelName, channelId) { React.render( <SidebarRightMenu - teamDisplayName={teamName} - teamType={teamType} + teamDisplayName={props.TeamDisplayName} + teamType={props.TeamType} />, document.getElementById('sidebar-menu') ); @@ -226,6 +223,11 @@ function setupChannelPage(teamName, teamType, teamId, channelName, channelId) { />, document.getElementById('file_upload_overlay') ); + + React.render( + <RegisterAppModal />, + document.getElementById('register_app_modal') + ); } global.window.setup_channel_page = setupChannelPage; diff --git a/web/react/pages/home.jsx b/web/react/pages/home.jsx index 18553542c..2299c306e 100644 --- a/web/react/pages/home.jsx +++ b/web/react/pages/home.jsx @@ -4,12 +4,12 @@ var ChannelStore = require('../stores/channel_store.jsx'); var Constants = require('../utils/constants.jsx'); -function setupHomePage(teamURL) { +function setupHomePage(props) { var last = ChannelStore.getLastVisitedName(); if (last == null || last.length === 0) { - window.location = teamURL + '/channels/' + Constants.DEFAULT_CHANNEL; + window.location = props.TeamURL + '/channels/' + Constants.DEFAULT_CHANNEL; } else { - window.location = teamURL + '/channels/' + last; + window.location = props.TeamURL + '/channels/' + last; } } diff --git a/web/react/pages/login.jsx b/web/react/pages/login.jsx index 424ae0e84..830f622fa 100644 --- a/web/react/pages/login.jsx +++ b/web/react/pages/login.jsx @@ -3,12 +3,12 @@ var Login = require('../components/login.jsx'); -function setupLoginPage(teamDisplayName, teamName, authServices) { +function setupLoginPage(props) { React.render( <Login - teamDisplayName={teamDisplayName} - teamName={teamName} - authServices={authServices} + teamDisplayName={props.TeamDisplayName} + teamName={props.TeamName} + authServices={props.AuthServices} />, document.getElementById('login') ); diff --git a/web/react/pages/password_reset.jsx b/web/react/pages/password_reset.jsx index 2ca468bea..b7bfdcd5e 100644 --- a/web/react/pages/password_reset.jsx +++ b/web/react/pages/password_reset.jsx @@ -3,14 +3,14 @@ var PasswordReset = require('../components/password_reset.jsx'); -function setupPasswordResetPage(isReset, teamDisplayName, teamName, hash, data) { +function setupPasswordResetPage(props) { React.render( <PasswordReset - isReset={isReset} - teamDisplayName={teamDisplayName} - teamName={teamName} - hash={hash} - data={data} + isReset={props.IsReset} + teamDisplayName={props.TeamDisplayName} + teamName={props.TeamName} + hash={props.Hash} + data={props.Data} />, document.getElementById('reset') ); diff --git a/web/react/pages/signup_team.jsx b/web/react/pages/signup_team.jsx index e9e803aa4..427daf577 100644 --- a/web/react/pages/signup_team.jsx +++ b/web/react/pages/signup_team.jsx @@ -3,12 +3,8 @@ var SignupTeam = require('../components/signup_team.jsx'); -var AsyncClient = require('../utils/async_client.jsx'); - -function setupSignupTeamPage(authServices) { - AsyncClient.getConfig(); - - var services = JSON.parse(authServices); +function setupSignupTeamPage(props) { + var services = JSON.parse(props.AuthServices); React.render( <SignupTeam services={services} />, diff --git a/web/react/pages/signup_team_complete.jsx b/web/react/pages/signup_team_complete.jsx index 72f9992a8..ec77e6602 100644 --- a/web/react/pages/signup_team_complete.jsx +++ b/web/react/pages/signup_team_complete.jsx @@ -3,12 +3,12 @@ var SignupTeamComplete = require('../components/signup_team_complete.jsx'); -function setupSignupTeamCompletePage(email, data, hash) { +function setupSignupTeamCompletePage(props) { React.render( <SignupTeamComplete - email={email} - hash={hash} - data={data} + email={props.Email} + hash={props.Hash} + data={props.Data} />, document.getElementById('signup-team-complete') ); diff --git a/web/react/pages/signup_user_complete.jsx b/web/react/pages/signup_user_complete.jsx index eaf93a61c..112aaa3f2 100644 --- a/web/react/pages/signup_user_complete.jsx +++ b/web/react/pages/signup_user_complete.jsx @@ -3,16 +3,16 @@ var SignupUserComplete = require('../components/signup_user_complete.jsx'); -function setupSignupUserCompletePage(email, name, uiName, id, data, hash, authServices) { +function setupSignupUserCompletePage(props) { React.render( <SignupUserComplete - teamId={id} - teamName={name} - teamDisplayName={uiName} - email={email} - hash={hash} - data={data} - authServices={authServices} + teamId={props.TeamId} + teamName={props.TeamName} + teamDisplayName={props.TeamDisplayName} + email={props.Email} + hash={props.Hash} + data={props.Data} + authServices={props.AuthServices} />, document.getElementById('signup-user-complete') ); diff --git a/web/react/pages/verify.jsx b/web/react/pages/verify.jsx index 7077b40b8..e48471bbd 100644 --- a/web/react/pages/verify.jsx +++ b/web/react/pages/verify.jsx @@ -3,12 +3,12 @@ var EmailVerify = require('../components/email_verify.jsx'); -global.window.setupVerifyPage = function setupVerifyPage(isVerified, teamURL, userEmail) { +global.window.setupVerifyPage = function setupVerifyPage(props) { React.render( <EmailVerify - isVerified={isVerified} - teamURL={teamURL} - userEmail={userEmail} + isVerified={props.IsVerified} + teamURL={props.TeamURL} + userEmail={props.UserEmail} />, document.getElementById('verify') ); diff --git a/web/react/stores/admin_store.jsx b/web/react/stores/admin_store.jsx new file mode 100644 index 000000000..591b52d05 --- /dev/null +++ b/web/react/stores/admin_store.jsx @@ -0,0 +1,58 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); +var EventEmitter = require('events').EventEmitter; + +var Constants = require('../utils/constants.jsx'); +var ActionTypes = Constants.ActionTypes; + +var LOG_CHANGE_EVENT = 'log_change'; + +class AdminStoreClass extends EventEmitter { + constructor() { + super(); + + this.logs = null; + + this.emitLogChange = this.emitLogChange.bind(this); + this.addLogChangeListener = this.addLogChangeListener.bind(this); + this.removeLogChangeListener = this.removeLogChangeListener.bind(this); + } + + emitLogChange() { + this.emit(LOG_CHANGE_EVENT); + } + + addLogChangeListener(callback) { + this.on(LOG_CHANGE_EVENT, callback); + } + + removeLogChangeListener(callback) { + this.removeListener(LOG_CHANGE_EVENT, callback); + } + + getLogs() { + return this.logs; + } + + saveLogs(logs) { + this.logs = logs; + } +} + +var AdminStore = new AdminStoreClass(); + +AdminStoreClass.dispatchToken = AppDispatcher.register((payload) => { + var action = payload.action; + + switch (action.type) { + case ActionTypes.RECIEVED_LOGS: + AdminStore.saveLogs(action.logs); + AdminStore.emitLogChange(); + break; + default: + } +}); + +export default AdminStore; diff --git a/web/react/stores/config_store.jsx b/web/react/stores/config_store.jsx deleted file mode 100644 index b397937be..000000000 --- a/web/react/stores/config_store.jsx +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. -// See License.txt for license information. - -var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); -var EventEmitter = require('events').EventEmitter; - -var BrowserStore = require('../stores/browser_store.jsx'); - -var Constants = require('../utils/constants.jsx'); -var ActionTypes = Constants.ActionTypes; - -var CHANGE_EVENT = 'change'; - -class ConfigStoreClass extends EventEmitter { - constructor() { - super(); - - this.emitChange = this.emitChange.bind(this); - this.addChangeListener = this.addChangeListener.bind(this); - this.removeChangeListener = this.removeChangeListener.bind(this); - this.getSetting = this.getSetting.bind(this); - this.getSettingAsBoolean = this.getSettingAsBoolean.bind(this); - this.updateStoredSettings = this.updateStoredSettings.bind(this); - } - emitChange() { - this.emit(CHANGE_EVENT); - } - addChangeListener(callback) { - this.on(CHANGE_EVENT, callback); - } - removeChangeListener(callback) { - this.removeListener(CHANGE_EVENT, callback); - } - getSetting(key, defaultValue) { - return BrowserStore.getItem('config_' + key, defaultValue); - } - getSettingAsBoolean(key, defaultValue) { - var value = this.getSetting(key, defaultValue); - - if (typeof value !== 'string') { - return Boolean(value); - } - - return value === 'true'; - } - updateStoredSettings(settings) { - for (let key in settings) { - if (settings.hasOwnProperty(key)) { - BrowserStore.setItem('config_' + key, settings[key]); - } - } - } -} - -var ConfigStore = new ConfigStoreClass(); - -ConfigStore.dispatchToken = AppDispatcher.register(function registry(payload) { - var action = payload.action; - - switch (action.type) { - case ActionTypes.RECIEVED_CONFIG: - ConfigStore.updateStoredSettings(action.settings); - ConfigStore.emitChange(); - break; - default: - } -}); - -export default ConfigStore; diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx index 5ffe65021..29ce47300 100644 --- a/web/react/stores/post_store.jsx +++ b/web/react/stores/post_store.jsx @@ -297,6 +297,7 @@ class PostStoreClass extends EventEmitter { post.message = '(message deleted)'; post.state = Constants.POST_DELETED; + post.filenames = []; posts[post.id] = post; this.storeUnseenDeletedPosts(post.channel_id, posts); diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx index 6ccef0506..3e23e5c33 100644 --- a/web/react/utils/async_client.jsx +++ b/web/react/utils/async_client.jsx @@ -319,6 +319,32 @@ export function getAudits() { ); } +export function getLogs() { + if (isCallInProgress('getLogs')) { + return; + } + + callTracker.getLogs = utils.getTimestamp(); + client.getLogs( + (data, textStatus, xhr) => { + callTracker.getLogs = 0; + + if (xhr.status === 304 || !data) { + return; + } + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECIEVED_LOGS, + logs: data + }); + }, + (err) => { + callTracker.getLogs = 0; + dispatchError(err, 'getLogs'); + } + ); +} + export function findTeams(email) { if (isCallInProgress('findTeams_' + email)) { return; @@ -556,28 +582,4 @@ export function getMyTeam() { dispatchError(err, 'getMyTeam'); } ); -} - -export function getConfig() { - if (isCallInProgress('getConfig')) { - return; - } - - callTracker.getConfig = utils.getTimestamp(); - client.getConfig( - function getConfigSuccess(data, textStatus, xhr) { - callTracker.getConfig = 0; - - if (data && xhr.status !== 304) { - AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_CONFIG, - settings: data - }); - } - }, - function getConfigFailure(err) { - callTracker.getConfig = 0; - dispatchError(err, 'getConfig'); - } - ); -} +}
\ No newline at end of file diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 51fd16474..ba3042d78 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -14,8 +14,6 @@ export function trackPage() { } function handleError(methodName, xhr, status, err) { - var LTracker = global.window.LTracker || []; - var e = null; try { e = JSON.parse(xhr.responseText); @@ -39,7 +37,6 @@ function handleError(methodName, xhr, status, err) { console.error(msg); //eslint-disable-line no-console console.error(e); //eslint-disable-line no-console - LTracker.push(msg); track('api', 'api_weberror', methodName, 'message', msg); @@ -294,6 +291,20 @@ export function getAudits(userId, success, error) { }); } +export function getLogs(success, error) { + $.ajax({ + url: '/api/v1/admin/logs', + dataType: 'json', + contentType: 'application/json', + type: 'GET', + success: success, + error: function onError(xhr, status, err) { + var e = handleError('getLogs', xhr, status, err); + error(e); + } + }); +} + export function getMeSynchronous(success, error) { var currentUser = null; $.ajax({ @@ -977,16 +988,35 @@ export function updateValetFeature(data, success, error) { track('api', 'api_teams_update_valet_feature'); } -export function getConfig(success, error) { +export function registerOAuthApp(app, success, error) { $.ajax({ - url: '/api/v1/config/get_all', + url: '/api/v1/oauth/register', dataType: 'json', + contentType: 'application/json', + type: 'POST', + data: JSON.stringify(app), + success: success, + error: (xhr, status, err) => { + const e = handleError('registerApp', xhr, status, err); + error(e); + } + }); + + module.exports.track('api', 'api_apps_register'); +} + +export function allowOAuth2(responseType, clientId, redirectUri, state, scope, success, error) { + $.ajax({ + url: '/api/v1/oauth/allow?response_type=' + responseType + '&client_id=' + clientId + '&redirect_uri=' + redirectUri + '&scope=' + scope + '&state=' + state, + dataType: 'json', + contentType: 'application/json', type: 'GET', - ifModified: true, success: success, - error: function onError(xhr, status, err) { - var e = handleError('getConfig', xhr, status, err); + error: (xhr, status, err) => { + const e = handleError('allowOAuth2', xhr, status, err); error(e); } }); + + module.exports.track('api', 'api_users_allow_oauth2'); } diff --git a/web/react/utils/config.js b/web/react/utils/config.js deleted file mode 100644 index c7d1aa2bc..000000000 --- a/web/react/utils/config.js +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. -// See License.txt for license information. - -export var config = { - - // Loggly configs - LogglyWriteKey: '', - LogglyConsoleErrors: true, - - // Segment configs - SegmentWriteKey: '', - - // Feature switches - AllowPublicLink: true, - AllowInviteNames: true, - RequireInviteNames: false, - AllowSignupDomainsWizard: false, - - // Google Developer Key (for Youtube API links) - // Leave blank to disable - GoogleDeveloperKey: '', - - // Privacy switches - ShowEmail: true, - - // Links - TermsLink: '/static/help/configure_links.html', - PrivacyLink: '/static/help/configure_links.html', - AboutLink: '/static/help/configure_links.html', - HelpLink: '/static/help/configure_links.html', - ReportProblemLink: '/static/help/configure_links.html', - HomeLink: '', - - // Toggle whether or not users are shown a message about agreeing to the Terms of Service during the signup process - ShowTermsDuringSignup: false, - - ThemeColors: ['#2389d7', '#008a17', '#dc4fad', '#ac193d', '#0072c6', '#d24726', '#ff8f32', '#82ba00', '#03b3b2', '#008299', '#4617b4', '#8c0095', '#004b8b', '#004b8b', '#570000', '#380000', '#585858', '#000000'] -}; - -// Flavor strings -export var strings = { - Team: 'team', - TeamPlural: 'teams', - Company: 'company', - CompanyPlural: 'companies' -}; - -global.window.config = config; diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 7ead079d7..03e4635b5 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -34,7 +34,9 @@ module.exports = { CLICK_TEAM: null, RECIEVED_TEAM: null, - RECIEVED_CONFIG: null + RECIEVED_CONFIG: null, + + RECIEVED_LOGS: null }), PayloadSources: keyMirror({ diff --git a/web/react/utils/text_formatting.jsx b/web/react/utils/text_formatting.jsx index 2c67d7a46..2025e16da 100644 --- a/web/react/utils/text_formatting.jsx +++ b/web/react/utils/text_formatting.jsx @@ -56,7 +56,7 @@ function autolinkUrls(text, tokens) { const linkText = match.getMatchedText(); let url = linkText; - if (!url.startsWith('http')) { + if (!url.lastIndexOf('http', 0) === 0) { url = `http://${linkText}`; } @@ -160,7 +160,7 @@ function autolinkHashtags(text, tokens) { var newTokens = new Map(); for (const [alias, token] of tokens) { - if (token.originalText.startsWith('#')) { + if (token.originalText.lastIndexOf('#', 0) === 0) { const index = tokens.size + newTokens.size; const newAlias = `__MM_HASHTAG${index}__`; diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 85c6137a7..032cf4ff4 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -9,7 +9,6 @@ var ActionTypes = Constants.ActionTypes; var AsyncClient = require('./async_client.jsx'); var client = require('./client.jsx'); var Autolinker = require('autolinker'); -import {config} from '../utils/config.js'; export function isEmail(email) { var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/; @@ -295,12 +294,12 @@ function getYoutubeEmbed(link) { $('.post-list-holder-by-time').scrollTop($('.post-list-holder-by-time')[0].scrollHeight); } - if (config.GoogleDeveloperKey) { + if (global.window.config.GoogleDeveloperKey) { $.ajax({ async: true, url: 'https://www.googleapis.com/youtube/v3/videos', type: 'GET', - data: {part: 'snippet', id: youtubeId, key: config.GoogleDeveloperKey}, + data: {part: 'snippet', id: youtubeId, key: global.window.config.GoogleDeveloperKey}, success: success }); } @@ -934,6 +933,6 @@ export function getTeamURLFromAddressBar() { export function getShortenedTeamURL() { const teamURL = getTeamURLFromAddressBar(); if (teamURL.length > 24) { - return teamURL.substring(0, 10) + '...' + teamURL.substring(teamURL.length - 12, teamURL.length - 1) + '/'; + return teamURL.substring(0, 10) + '...' + teamURL.substring(teamURL.length - 12, teamURL.length) + '/'; } } |