Merge branch 'master' into PLT-12-log

Этот коммит содержится в:
=Corey Hulen
2015-09-14 16:59:24 -07:00
родитель e06e292be7 bfebb41bc0
Коммит 3bd350dcaf
59 изменённых файлов: 18560 добавлений и 731 удалений

Просмотреть файл

@@ -18,7 +18,8 @@
"es6": true
},
"globals": {
"React": false
"React": false,
"ReactBootstrap": false
},
"rules": {
"comma-dangle": [2, "never"],

177
web/react/components/change_url_modal.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,177 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var Modal = ReactBootstrap.Modal;
var Utils = require('../utils/utils.jsx');
export default class ChangeUrlModal extends React.Component {
constructor(props) {
super(props);
this.onURLChanged = this.onURLChanged.bind(this);
this.doSubmit = this.doSubmit.bind(this);
this.doCancel = this.doCancel.bind(this);
this.state = {
currentURL: props.currentURL,
urlError: '',
userEdit: false
};
}
componentWillReceiveProps(nextProps) {
// This check prevents the url being deleted when we re-render
// because of user status check
if (!this.state.userEdit) {
this.setState({
currentURL: nextProps.currentURL
});
}
}
componentDidUpdate(prevProps) {
if (this.props.show === true && prevProps.show === false) {
React.findDOMNode(this.refs.urlinput).select();
}
}
onURLChanged(e) {
const url = e.target.value.trim();
this.setState({currentURL: url.replace(/[^A-Za-z0-9-_]/g, '').toLowerCase(), userEdit: true});
}
getURLError(url) {
let error = []; //eslint-disable-line prefer-const
if (url.length < 2) {
error.push(<span key='error1'>{'Must be longer than two characters'}<br/></span>);
}
if (url.charAt(0) === '-' || url.charAt(0) === '_') {
error.push(<span key='error2'>{'Must start with a letter or number'}<br/></span>);
}
if (url.length > 1 && (url.charAt(url.length - 1) === '-' || url.charAt(url.length - 1) === '_')) {
error.push(<span key='error3'>{'Must end with a letter or number'}<br/></span>);
}
if (url.indexOf('__') > -1) {
error.push(<span key='error4'>{'Can not contain two underscores in a row.'}<br/></span>);
}
// In case of error we don't detect
if (error.length === 0) {
error.push(<span key='errorlast'>{'Invalid URL'}<br/></span>);
}
return error;
}
doSubmit(e) {
e.preventDefault();
const url = React.findDOMNode(this.refs.urlinput).value;
const cleanedURL = Utils.cleanUpUrlable(url);
if (cleanedURL !== url || url.length < 2 || url.indexOf('__') > -1) {
this.setState({urlError: this.getURLError(url)});
return;
}
this.setState({urlError: '', userEdit: false});
this.props.onModalSubmit(url);
}
doCancel() {
this.setState({urlError: '', userEdit: false});
this.props.onModalDismissed();
}
render() {
let urlClass = 'input-group input-group--limit';
let urlError = null;
let serverError = null;
if (this.state.urlError) {
urlClass += ' has-error';
urlError = (<p className='input__help error'>{this.state.urlError}</p>);
}
if (this.props.serverError) {
serverError = <div className='form-group has-error'><p className='input__help error'>{this.props.serverError}</p></div>;
}
const fullTeamUrl = Utils.getTeamURLFromAddressBar();
const teamURL = Utils.getShortenedTeamURL();
return (
<Modal
show={this.props.show}
onHide={this.doCancel}
>
<Modal.Header closeButton={true}>
<Modal.Title>{this.props.title}</Modal.Title>
</Modal.Header>
<form
role='form'
className='form-horizontal'
>
<Modal.Body>
<div className='modal-intro'>{this.props.description}</div>
<div className='form-group'>
<label className='col-sm-2 form__label control-label'>{this.props.urlLabel}</label>
<div className='col-sm-10'>
<div className={urlClass}>
<span
data-toggle='tooltip'
title={fullTeamUrl}
className='input-group-addon'
>
{teamURL}
</span>
<input
type='text'
ref='urlinput'
className='form-control'
maxLength='22'
onChange={this.onURLChanged}
value={this.state.currentURL}
autoFocus={true}
tabIndex='1'
/>
</div>
{urlError}
{serverError}
</div>
</div>
</Modal.Body>
<Modal.Footer>
<button
type='button'
className='btn btn-default'
onClick={this.doCancel}
>
{'Close'}
</button>
<button
onClick={this.doSubmit}
type='submit'
className='btn btn-primary'
tabIndex='2'
>
{this.props.submitButtonText}
</button>
</Modal.Footer>
</form>
</Modal>
);
}
}
ChangeUrlModal.defaultProps = {
show: false,
title: 'Change URL',
desciption: '',
urlLabel: 'URL',
submitButtonText: 'Submit',
currentURL: '',
serverError: ''
};
ChangeUrlModal.propTypes = {
show: React.PropTypes.bool.isRequired,
title: React.PropTypes.string,
description: React.PropTypes.string,
urlLabel: React.PropTypes.string,
submitButtonText: React.PropTypes.string,
currentURL: React.PropTypes.string,
serverError: React.PropTypes.string,
onModalSubmit: React.PropTypes.func.isRequired,
onModalDismissed: React.PropTypes.func.isRequired
};

Просмотреть файл

@@ -20,12 +20,11 @@ export default class FileUpload extends React.Component {
}
fileUploadSuccess(channelId, data) {
var parsedData = $.parseJSON(data);
this.props.onFileUpload(parsedData.filenames, parsedData.client_ids, channelId);
this.props.onFileUpload(data.filenames, data.client_ids, channelId);
var requests = this.state.requests;
for (var j = 0; j < parsedData.client_ids.length; j++) {
delete requests[parsedData.client_ids[j]];
for (var j = 0; j < data.client_ids.length; j++) {
delete requests[data.client_ids[j]];
}
this.setState({requests: requests});
}
@@ -53,7 +52,7 @@ export default class FileUpload extends React.Component {
}
// generate a unique id that can be used by other components to refer back to this upload
var clientId = utils.generateId();
let clientId = utils.generateId();
// prepare data to be uploaded
var formData = new FormData();

Просмотреть файл

@@ -1,211 +0,0 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx');
var asyncClient = require('../utils/async_client.jsx');
var UserStore = require('../stores/user_store.jsx');
export default class NewChannelModal extends React.Component {
constructor() {
super();
this.handleSubmit = this.handleSubmit.bind(this);
this.displayNameKeyUp = this.displayNameKeyUp.bind(this);
this.handleClose = this.handleClose.bind(this);
this.state = {channelType: ''};
}
handleSubmit(e) {
e.preventDefault();
var channel = {};
var state = {serverError: ''};
channel.display_name = React.findDOMNode(this.refs.display_name).value.trim();
if (!channel.display_name) {
state.displayNameError = 'This field is required';
state.inValid = true;
} else if (channel.display_name.length > 22) {
state.displayNameError = 'This field must be less than 22 characters';
state.inValid = true;
} else {
state.displayNameError = '';
}
channel.name = React.findDOMNode(this.refs.channel_name).value.trim();
if (!channel.name) {
state.nameError = 'This field is required';
state.inValid = true;
} else if (channel.name.length > 22) {
state.nameError = 'This field must be less than 22 characters';
state.inValid = true;
} else {
var cleanedName = utils.cleanUpUrlable(channel.name);
if (cleanedName !== channel.name) {
state.nameError = "Must be lowercase alphanumeric characters, allowing '-' but not starting or ending with '-'";
state.inValid = true;
} else {
state.nameError = '';
}
}
this.setState(state);
if (state.inValid) {
return;
}
var cu = UserStore.getCurrentUser();
channel.team_id = cu.team_id;
channel.description = React.findDOMNode(this.refs.channel_desc).value.trim();
channel.type = this.state.channelType;
client.createChannel(channel,
function success(data) {
$(React.findDOMNode(this.refs.modal)).modal('hide');
asyncClient.getChannel(data.id);
utils.switchChannel(data);
React.findDOMNode(this.refs.display_name).value = '';
React.findDOMNode(this.refs.channel_name).value = '';
React.findDOMNode(this.refs.channel_desc).value = '';
}.bind(this),
function error(err) {
state.serverError = err.message;
state.inValid = true;
this.setState(state);
}.bind(this)
);
}
displayNameKeyUp() {
var displayName = React.findDOMNode(this.refs.display_name).value.trim();
var channelName = utils.cleanUpUrlable(displayName);
React.findDOMNode(this.refs.channel_name).value = channelName;
}
componentDidMount() {
var self = this;
$(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function onModalShow(e) {
var button = e.relatedTarget;
self.setState({channelType: $(button).attr('data-channeltype')});
});
$(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose);
}
componentWillUnmount() {
$(React.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose);
}
handleClose() {
$(React.findDOMNode(this)).find('.form-control').each(function clearForms() {
this.value = '';
});
this.setState({channelType: '', displayNameError: '', nameError: '', serverError: '', inValid: false});
}
render() {
var displayNameError = null;
var nameError = null;
var serverError = null;
var displayNameClass = 'form-group';
var nameClass = 'form-group';
if (this.state.displayNameError) {
displayNameError = <label className='control-label'>{this.state.displayNameError}</label>;
displayNameClass += ' has-error';
}
if (this.state.nameError) {
nameError = <label className='control-label'>{this.state.nameError}</label>;
nameClass += ' has-error';
}
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
var channelTerm = 'Channel';
if (this.state.channelType === 'P') {
channelTerm = 'Group';
}
return (
<div
className='modal fade'
id='new_channel'
ref='modal'
tabIndex='-1'
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'
>
<span aria-hidden='true'>&times;</span>
<span className='sr-only'>Cancel</span>
</button>
<h4 className='modal-title'>New {channelTerm}</h4>
</div>
<form role='form'>
<div className='modal-body'>
<div className={displayNameClass}>
<label className='control-label'>Display Name</label>
<input
onKeyUp={this.displayNameKeyUp}
type='text'
ref='display_name'
className='form-control'
placeholder='Enter display name'
maxLength='22'
/>
{displayNameError}
</div>
<div className={nameClass}>
<label className='control-label'>Handle</label>
<input
type='text'
className='form-control'
ref='channel_name'
placeholder="lowercase alphanumeric's only"
maxLength='22'
/>
{nameError}
</div>
<div className='form-group'>
<label className='control-label'>Description</label>
<textarea
className='form-control no-resize'
ref='channel_desc'
rows='3'
placeholder='Description'
maxLength='1024'
/>
</div>
{serverError}
</div>
<div className='modal-footer'>
<button
type='button'
className='btn btn-default'
data-dismiss='modal'
>
Cancel
</button>
<button
onClick={this.handleSubmit}
type='submit'
className='btn btn-primary'
>
Create New {channelTerm}
</button>
</div>
</form>
</div>
</div>
</div>
);
}
}

206
web/react/components/new_channel_flow.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,206 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var Utils = require('../utils/utils.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var Client = require('../utils/client.jsx');
var UserStore = require('../stores/user_store.jsx');
var NewChannelModal = require('./new_channel_modal.jsx');
var ChangeURLModal = require('./change_url_modal.jsx');
const SHOW_NEW_CHANNEL = 1;
const SHOW_EDIT_URL = 2;
const SHOW_EDIT_URL_THEN_COMPLETE = 3;
export default class NewChannelFlow extends React.Component {
constructor(props) {
super(props);
this.doSubmit = this.doSubmit.bind(this);
this.typeSwitched = this.typeSwitched.bind(this);
this.urlChangeRequested = this.urlChangeRequested.bind(this);
this.urlChangeSubmitted = this.urlChangeSubmitted.bind(this);
this.urlChangeDismissed = this.urlChangeDismissed.bind(this);
this.channelDataChanged = this.channelDataChanged.bind(this);
this.state = {
serverError: '',
channelType: 'O',
flowState: SHOW_NEW_CHANNEL,
channelDisplayName: '',
channelName: '',
channelDescription: '',
nameModified: false
};
}
componentWillReceiveProps(nextProps) {
// If we are being shown, grab channel type from props and clear
if (nextProps.show === true && this.props.show === false) {
this.setState({
serverError: '',
channelType: nextProps.channelType,
flowState: SHOW_NEW_CHANNEL,
channelDisplayName: '',
channelName: '',
channelDescription: '',
nameModified: false
});
}
}
doSubmit() {
var channel = {};
channel.display_name = this.state.channelDisplayName;
if (!channel.display_name) {
this.setState({serverError: 'Invalid Channel Name'});
return;
}
channel.name = this.state.channelName;
if (channel.name.length < 2) {
this.setState({flowState: SHOW_EDIT_URL_THEN_COMPLETE});
return;
}
const cu = UserStore.getCurrentUser();
channel.team_id = cu.team_id;
channel.description = this.state.channelDescription;
channel.type = this.state.channelType;
Client.createChannel(channel,
(data) => {
this.props.onModalDismissed();
AsyncClient.getChannel(data.id);
Utils.switchChannel(data);
},
(err) => {
if (err.message === 'Name must be 2 or more lowercase alphanumeric characters') {
this.setState({flowState: SHOW_EDIT_URL_THEN_COMPLETE});
}
if (err.message === 'A channel with that handle already exists') {
this.setState({serverError: 'A channel with that URL already exists'});
return;
}
this.setState({serverError: err.message});
}
);
}
typeSwitched() {
if (this.state.channelType === 'P') {
this.setState({channelType: 'O'});
} else {
this.setState({channelType: 'P'});
}
}
urlChangeRequested() {
this.setState({flowState: SHOW_EDIT_URL});
}
urlChangeSubmitted(newURL) {
if (this.state.flowState === SHOW_EDIT_URL_THEN_COMPLETE) {
this.setState({channelName: newURL, nameModified: true}, this.doSubmit);
} else {
this.setState({flowState: SHOW_NEW_CHANNEL, serverError: '', channelName: newURL, nameModified: true});
}
}
urlChangeDismissed() {
this.setState({flowState: SHOW_NEW_CHANNEL});
}
channelDataChanged(data) {
this.setState({
channelDisplayName: data.displayName,
channelDescription: data.description
});
if (!this.state.nameModified) {
this.setState({channelName: Utils.cleanUpUrlable(data.displayName.trim())});
}
}
render() {
const channelData = {
name: this.state.channelName,
displayName: this.state.channelDisplayName,
description: this.state.channelDescription
};
let showChannelModal = false;
let showGroupModal = false;
let showChangeURLModal = false;
let changeURLTitle = '';
let changeURLSubmitButtonText = '';
let channelTerm = '';
// Only listen to flow state if we are being shown
if (this.props.show) {
switch (this.state.flowState) {
case SHOW_NEW_CHANNEL:
if (this.state.channelType === 'O') {
showChannelModal = true;
channelTerm = 'Channel';
} else {
showGroupModal = true;
channelTerm = 'Group';
}
break;
case SHOW_EDIT_URL:
showChangeURLModal = true;
changeURLTitle = 'Change ' + channelTerm + ' URL';
changeURLSubmitButtonText = 'Change ' + channelTerm + ' URL';
break;
case SHOW_EDIT_URL_THEN_COMPLETE:
showChangeURLModal = true;
changeURLTitle = 'Set ' + channelTerm + ' URL';
changeURLSubmitButtonText = 'Create ' + channelTerm;
break;
}
}
return (
<span>
<NewChannelModal
show={showChannelModal}
channelType={'O'}
channelData={channelData}
serverError={this.state.serverError}
onSubmitChannel={this.doSubmit}
onModalDismissed={this.props.onModalDismissed}
onTypeSwitched={this.typeSwitched}
onChangeURLPressed={this.urlChangeRequested}
onDataChanged={this.channelDataChanged}
/>
<NewChannelModal
show={showGroupModal}
channelType={'P'}
channelData={channelData}
serverError={this.state.serverError}
onSubmitChannel={this.doSubmit}
onModalDismissed={this.props.onModalDismissed}
onTypeSwitched={this.typeSwitched}
onChangeURLPressed={this.urlChangeRequested}
onDataChanged={this.channelDataChanged}
/>
<ChangeURLModal
show={showChangeURLModal}
title={changeURLTitle}
description={'Some characters are not allowed in URLs and may be removed.'}
urlLabel={channelTerm + ' URL'}
submitButtonText={changeURLSubmitButtonText}
currentURL={this.state.channelName}
serverError={this.state.serverError}
onModalSubmit={this.urlChangeSubmitted}
onModalDismissed={this.urlChangeDismissed}
/>
</span>
);
}
}
NewChannelFlow.defaultProps = {
show: false,
channelType: 'O'
};
NewChannelFlow.propTypes = {
show: React.PropTypes.bool.isRequired,
channelType: React.PropTypes.string.isRequired,
onModalDismissed: React.PropTypes.func.isRequired
};

198
web/react/components/new_channel_modal.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,198 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
const Utils = require('../utils/utils.jsx');
var Modal = ReactBootstrap.Modal;
export default class NewChannelModal extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
this.state = {
displayNameError: ''
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.show === true && this.props.show === false) {
this.setState({
displayNameError: ''
});
}
}
handleSubmit(e) {
e.preventDefault();
const displayName = React.findDOMNode(this.refs.display_name).value.trim();
if (displayName.length < 1) {
this.setState({displayNameError: 'This field is required'});
return;
}
this.props.onSubmitChannel();
}
handleChange() {
const newData = {
displayName: React.findDOMNode(this.refs.display_name).value,
description: React.findDOMNode(this.refs.channel_desc).value
};
this.props.onDataChanged(newData);
}
render() {
var displayNameError = null;
var serverError = null;
var displayNameClass = 'form-group';
if (this.state.displayNameError) {
displayNameError = <p className='input__help error'>{this.state.displayNameError}</p>;
displayNameClass += ' has-error';
}
if (this.props.serverError) {
serverError = <div className='form-group has-error'><p className='input__help error'>{this.props.serverError}</p></div>;
}
var channelTerm = '';
var channelSwitchText = '';
switch (this.props.channelType) {
case 'P':
channelTerm = 'Group';
channelSwitchText = (
<div className='modal-intro'>
{'Create a new private group with restricted membership. '}
<a
href='#'
onClick={this.props.onTypeSwitched}
>
{'Create a public channel'}
</a>
</div>
);
break;
case 'O':
channelTerm = 'Channel';
channelSwitchText = (
<div className='modal-intro'>
{'Create a new public channel anyone can join. '}
<a
href='#'
onClick={this.props.onTypeSwitched}
>
{'Create a private group'}
</a>
</div>
);
break;
}
const prettyTeamURL = Utils.getShortenedTeamURL();
return (
<span>
<Modal
show={this.props.show}
onHide={this.props.onModalDismissed}
>
<Modal.Header closeButton={true}>
<Modal.Title>{'New ' + channelTerm}</Modal.Title>
</Modal.Header>
<form
role='form'
className='form-horizontal'
>
<Modal.Body>
<div>
{channelSwitchText}
</div>
<div className={displayNameClass}>
<label className='col-sm-2 form__label control-label'>{'Name'}</label>
<div className='col-sm-10'>
<input
onChange={this.handleChange}
type='text'
ref='display_name'
className='form-control'
placeholder='Ex: "Bugs", "Marketing", "办公室恋情"'
maxLength='22'
value={this.props.channelData.displayName}
autoFocus={true}
tabIndex='1'
/>
{displayNameError}
<p className='input__help'>
{'Channel URL: ' + prettyTeamURL + this.props.channelData.name + ' ('}
<a
href='#'
onClick={this.props.onChangeURLPressed}
>
{'change this URL'}
</a>
{')'}
</p>
</div>
</div>
<div className='form-group less'>
<div className='col-sm-2'>
<label className='form__label control-label'>{'Description'}</label>
<label className='form__label light'>{'(optional)'}</label>
</div>
<div className='col-sm-10'>
<textarea
className='form-control no-resize'
ref='channel_desc'
rows='4'
placeholder='Description'
maxLength='1024'
value={this.props.channelData.description}
onChange={this.handleChange}
tabIndex='2'
/>
<p className='input__help'>
{'The purpose of your channel. To help others decide whether to join.'}
</p>
{serverError}
</div>
</div>
</Modal.Body>
<Modal.Footer>
<button
type='button'
className='btn btn-default'
onClick={this.props.onModalDismissed}
>
{'Cancel'}
</button>
<button
onClick={this.handleSubmit}
type='submit'
className='btn btn-primary'
tabIndex='3'
>
{'Create New ' + channelTerm}
</button>
</Modal.Footer>
</form>
</Modal>
</span>
);
}
}
NewChannelModal.defaultProps = {
show: false,
channelType: 'O',
serverError: ''
};
NewChannelModal.propTypes = {
show: React.PropTypes.bool.isRequired,
channelType: React.PropTypes.string.isRequired,
channelData: React.PropTypes.object.isRequired,
serverError: React.PropTypes.string,
onSubmitChannel: React.PropTypes.func.isRequired,
onModalDismissed: React.PropTypes.func.isRequired,
onTypeSwitched: React.PropTypes.func.isRequired,
onChangeURLPressed: React.PropTypes.func.isRequired,
onDataChanged: React.PropTypes.func.isRequired
};

Просмотреть файл

@@ -152,7 +152,7 @@ export default class Post extends React.Component {
return (
<div>
<div
id={post.id}
id={'post_' + post.id}
className={'post ' + sameUserClass + ' ' + rootUser + ' ' + postType + ' ' + currentUserCss}
>
{profilePic}

Просмотреть файл

@@ -189,9 +189,15 @@ export default class PostList extends React.Component {
this.scrollToBottom(true);
// the user clicked 'load more messages'
} else if (this.gotMorePosts) {
var lastPost = oldPosts[oldOrder[prevState.numToDisplay]];
$('#' + lastPost.id)[0].scrollIntoView();
} else if (this.gotMorePosts && oldOrder.length > 0) {
let index;
if (prevState.numToDisplay >= oldOrder.length) {
index = oldOrder.length - 1;
} else {
index = prevState.numToDisplay;
}
const lastPost = oldPosts[oldOrder[index]];
$('#post_' + lastPost.id)[0].scrollIntoView();
this.gotMorePosts = false;
} else {
this.scrollTo(this.prevScrollTop);

Просмотреть файл

@@ -12,6 +12,7 @@ var Utils = require('../utils/utils.jsx');
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');
export default class Sidebar extends React.Component {
constructor(props) {
@@ -28,6 +29,7 @@ export default class Sidebar extends React.Component {
this.createChannelElement = this.createChannelElement.bind(this);
this.state = this.getStateFromStores();
this.state.modal = '';
this.state.loadingDMChannel = -1;
}
getStateFromStores() {
@@ -473,8 +475,18 @@ export default class Sidebar extends React.Component {
);
}
let showChannelModal = false;
if (this.state.modal !== '') {
showChannelModal = true;
}
return (
<div>
<NewChannelFlow
show={showChannelModal}
channelType={this.state.modal}
onModalDismissed={() => this.setState({modal: ''})}
/>
<SidebarHeader
teamDisplayName={this.props.teamDisplayName}
teamType={this.props.teamType}
@@ -508,11 +520,9 @@ export default class Sidebar extends React.Component {
<a
className='add-channel-btn'
href='#'
data-toggle='modal'
data-target='#new_channel'
data-channeltype='O'
onClick={() => this.setState({modal: 'O'})}
>
+
{'+'}
</a>
</h4>
</li>
@@ -537,11 +547,9 @@ export default class Sidebar extends React.Component {
<a
className='add-channel-btn'
href='#'
data-toggle='modal'
data-target='#new_channel'
data-channeltype='P'
onClick={() => this.setState({modal: 'P'})}
>
+
{'+'}
</a>
</h4>
</li>

Просмотреть файл

@@ -32,7 +32,7 @@ export default class SignupUserComplete extends React.Component {
handleSubmit(e) {
e.preventDefault();
this.state.user.username = React.findDOMNode(this.refs.name).value.trim();
this.state.user.username = React.findDOMNode(this.refs.name).value.trim().toLowerCase();
if (!this.state.user.username) {
this.setState({nameError: 'This field is required', emailError: '', passwordError: '', serverError: ''});
return;

Просмотреть файл

@@ -35,7 +35,7 @@ export default class TeamImportTab extends React.Component {
var uploadHelpText = (
<div>
<p>{'Slack does not allow you to export files, images, private groups or direct messages stored in Slack. Therefore, Slack import to Mattermost only supports importing of text messages in your Slack team\'\s public channels.'}</p>
<p>{'The Slack import to Mattermost is in "Preview". Slack bot posts and channels with underscores do not yet import.'}</p>
<p>{'The Slack import to Mattermost is in "Preview". Slack bot posts do not yet import.'}</p>
</div>
);

Просмотреть файл

@@ -22,7 +22,7 @@ export default class TeamSignupUsernamePage extends React.Component {
submitNext(e) {
e.preventDefault();
var name = React.findDOMNode(this.refs.name).value.trim();
var name = React.findDOMNode(this.refs.name).value.trim().toLowerCase();
var usernameError = Utils.isValidUsername(name);
if (usernameError === 'Cannot use a reserved word as a username.') {

Просмотреть файл

@@ -40,7 +40,7 @@ export default class UserSettingsGeneralTab extends React.Component {
e.preventDefault();
var user = this.props.user;
var username = this.state.username.trim();
var username = this.state.username.trim().toLowerCase();
var usernameError = utils.isValidUsername(username);
if (usernameError === 'Cannot use a reserved word as a username.') {

Просмотреть файл

@@ -105,6 +105,14 @@ export default class ViewImageModal extends React.Component {
this.loadImage(this.state.imgId);
}.bind(this));
$('#' + this.props.modalId).on('hidden.bs.modal', function onModalHide() {
if (this.refs.video) {
var video = React.findDOMNode(this.refs.video);
video.pause();
video.currentTime = 0;
}
}.bind(this));
$(React.findDOMNode(this.refs.modal)).click(function onModalClick(e) {
if (e.target === this || e.target === React.findDOMNode(this.refs.imageBody)) {
$('.image_modal').modal('hide');
@@ -211,6 +219,16 @@ export default class ViewImageModal extends React.Component {
/>
</a>
);
} else if (fileType === 'video' || fileType === 'audio') {
content = (
<video
ref='video'
data-setup='{}'
controls='controls'
>
<source src={Utils.getWindowLocationOrigin() + '/api/v1/files/get' + filename} />
</video>
);
} else {
// non-image files include a section providing details about the file
var infoString = 'File type ' + fileInfo.ext.toUpperCase();

Просмотреть файл

@@ -3,22 +3,21 @@
"version": "0.0.1",
"private": true,
"dependencies": {
"autolinker": "^0.18.1",
"flux": "^2.1.1",
"keymirror": "^0.1.1",
"object-assign": "^3.0.0",
"react": "^0.13.3",
"react-zeroclipboard-mixin": "^0.1.0",
"twemoji": "^1.4.1"
"autolinker": "0.18.1",
"flux": "2.1.1",
"keymirror": "0.1.1",
"object-assign": "3.0.0",
"react-zeroclipboard-mixin": "0.1.0",
"twemoji": "1.4.1"
},
"devDependencies": {
"browserify": "^11.0.1",
"envify": "^3.4.0",
"babelify": "^6.1.3",
"uglify-js": "^2.4.24",
"watchify": "^3.3.1",
"eslint": "^1.3.1",
"eslint-plugin-react": "^3.3.1"
"browserify": "11.0.1",
"envify": "3.4.0",
"babelify": "6.1.3",
"uglify-js": "2.4.24",
"watchify": "3.3.1",
"eslint": "1.3.1",
"eslint-plugin-react": "3.3.1"
},
"scripts": {
"start": "watchify --extension=jsx -o ../static/js/bundle.js -v -d ./**/*.jsx",

Просмотреть файл

@@ -17,7 +17,6 @@ var RenameChannelModal = require('../components/rename_channel_modal.jsx');
var EditPostModal = require('../components/edit_post_modal.jsx');
var DeletePostModal = require('../components/delete_post_modal.jsx');
var MoreChannelsModal = require('../components/more_channels.jsx');
var NewChannelModal = require('../components/new_channel.jsx');
var PostDeletedModal = require('../components/post_deleted_modal.jsx');
var ChannelNotificationsModal = require('../components/channel_notifications.jsx');
var UserSettingsModal = require('../components/user_settings_modal.jsx');
@@ -153,11 +152,6 @@ function setupChannelPage(teamName, teamType, teamId, channelName, channelId) {
document.getElementById('direct_channel_modal')
);
React.render(
<NewChannelModal />,
document.getElementById('new_channel_modal')
);
React.render(
<PostListContainer />,
document.getElementById('post-list')

Просмотреть файл

@@ -828,14 +828,12 @@ export function isValidUsername(name) {
} else if (name.length < 3 || name.length > 15) {
error = 'Must be between 3 and 15 characters';
} else if (!(/^[a-z0-9\.\-\_]+$/).test(name)) {
error = "Must contain only lowercase letters, numbers, and the symbols '.', '-', and '_'.";
error = "Must contain only letters, numbers, and the symbols '.', '-', and '_'.";
} else if (!(/[a-z]/).test(name.charAt(0))) {
error = 'First character must be a letter.';
} else {
var lowerName = name.toLowerCase().trim();
for (var i = 0; i < Constants.RESERVED_USERNAMES.length; i++) {
if (lowerName === Constants.RESERVED_USERNAMES[i]) {
if (name === Constants.RESERVED_USERNAMES[i]) {
error = 'Cannot use a reserved word as a username.';
break;
}
@@ -1127,3 +1125,14 @@ export function importSlack(file, success, error) {
client.importSlack(formData, success, error);
}
export function getTeamURLFromAddressBar() {
return window.location.href.split('/channels')[0];
}
export function getShortenedTeamURL() {
const teamURL = getTeamURLFromAddressBar();
if (teamURL.length > 24) {
return teamURL.substring(0, 10) + '...' + teamURL.substring(teamURL.length - 12, teamURL.length - 1) + '/';
}
}