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) + '/';
}
}

33
web/sass-files/sass/partials/_forms.scss Обычный файл
Просмотреть файл

@@ -0,0 +1,33 @@
.form-horizontal {
.modal-intro {
margin: -10px 0 30px;
}
.form__label {
text-align: left;
padding-right: 3px;
font-weight: bold;
font-size: 1.1em;
&.light {
color: #999;
font-size: 1.05em;
font-style: italic;
padding-top: 2px;
}
}
.input__help {
color: #777;
margin: 10px 0 0 10px;
&.error {
color: #a94442;
}
}
.form-control {
font-weight: normal;
}
.form-group {
margin-bottom: 25px;
&.less {
margin-bottom: 10px;
}
}
}

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

@@ -145,6 +145,8 @@
li a {
padding: 3px 20px;
color: #555;
text-overflow: ellipsis;
overflow: hidden;
}
}
.dropdown__icon {

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

@@ -123,6 +123,7 @@
}
.more-channel-table {
margin: 0;
table-layout: fixed;
p {
font-size: 0.9em;
overflow: hidden;
@@ -154,7 +155,7 @@
&.td--action {
text-align: right;
padding: 8px 15px 8px 8px;
width: 70px;
width: 80px;
vertical-align: middle;
}
}

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

@@ -12,7 +12,10 @@
}
.dropdown-menu {
max-height: 300px;
overflow: auto;
overflow-x: hidden;
overflow-y: auto;
max-width: 200px;
width: 200px;
}
.search__form {
margin: 0;
@@ -49,7 +52,7 @@
left: 0;
right: 0;
width: 72%;
color: #777;
color: #fff;
background: #2389D7;
@include border-radius(50px);
margin: 0 auto;

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

@@ -29,6 +29,7 @@
@import "partials/videos";
@import "partials/settings";
@import "partials/modal";
@import "partials/forms";
@import "partials/mentions";
@import "partials/command-box";
@import "partials/error";

5
web/static/css/bootstrap-3.3.1.min.css поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Разница между файлами не показана из-за своего большого размера Загрузить разницу

5
web/static/css/bootstrap-3.3.5.min.css поставляемый Обычный файл

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

7
web/static/js/bootstrap-3.3.1.min.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Разница между файлами не показана из-за своего большого размера Загрузить разницу

7
web/static/js/bootstrap-3.3.5.min.js поставляемый Обычный файл

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

1024
web/static/js/jasny-bootstrap.js Обычный файл

Разница между файлами не показана из-за своего большого размера Загрузить разницу

6
web/static/js/jasny-bootstrap.min.js поставляемый Обычный файл

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

15678
web/static/js/react-bootstrap-0.25.1.js Обычный файл

Разница между файлами не показана из-за своего большого размера Загрузить разницу

14
web/static/js/react-bootstrap-0.25.1.min.js поставляемый Обычный файл

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

16
web/static/js/react-with-addons-0.13.1.min.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

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

@@ -1,5 +1,5 @@
/**
* React (with addons) v0.13.1
* React (with addons) v0.13.3
*/
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.React = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
/**
@@ -710,7 +710,9 @@ var isUnitlessNumber = {
columnCount: true,
flex: true,
flexGrow: true,
flexPositive: true,
flexShrink: true,
flexNegative: true,
fontWeight: true,
lineClamp: true,
lineHeight: true,
@@ -723,7 +725,9 @@ var isUnitlessNumber = {
// SVG-related properties
fillOpacity: true,
strokeOpacity: true
strokeDashoffset: true,
strokeOpacity: true,
strokeWidth: true
};
/**
@@ -3788,6 +3792,7 @@ var HTMLDOMPropertyConfig = {
headers: null,
height: MUST_USE_ATTRIBUTE,
hidden: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
high: null,
href: null,
hrefLang: null,
htmlFor: null,
@@ -3798,6 +3803,7 @@ var HTMLDOMPropertyConfig = {
lang: null,
list: MUST_USE_ATTRIBUTE,
loop: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
low: null,
manifest: MUST_USE_ATTRIBUTE,
marginHeight: null,
marginWidth: null,
@@ -3812,6 +3818,7 @@ var HTMLDOMPropertyConfig = {
name: null,
noValidate: HAS_BOOLEAN_VALUE,
open: HAS_BOOLEAN_VALUE,
optimum: null,
pattern: null,
placeholder: null,
poster: null,
@@ -3825,6 +3832,7 @@ var HTMLDOMPropertyConfig = {
rowSpan: null,
sandbox: null,
scope: null,
scoped: HAS_BOOLEAN_VALUE,
scrolling: null,
seamless: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
selected: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
@@ -3866,7 +3874,9 @@ var HTMLDOMPropertyConfig = {
itemID: MUST_USE_ATTRIBUTE,
itemRef: MUST_USE_ATTRIBUTE,
// property is supported for OpenGraph in meta tags.
property: null
property: null,
// IE-only attribute that controls focus behavior
unselectable: MUST_USE_ATTRIBUTE
},
DOMAttributeNames: {
acceptCharset: 'accept-charset',
@@ -4475,7 +4485,7 @@ if ("production" !== "development") {
if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') {
console.debug(
'Download the React DevTools for a better development experience: ' +
'http://fb.me/react-devtools'
'https://fb.me/react-devtools'
);
}
}
@@ -4502,7 +4512,7 @@ if ("production" !== "development") {
if (!expectedFeatures[i]) {
console.error(
'One or more ES5 shim/shams expected by React are not available: ' +
'http://fb.me/react-warning-polyfills'
'https://fb.me/react-warning-polyfills'
);
break;
}
@@ -4510,7 +4520,7 @@ if ("production" !== "development") {
}
}
React.version = '0.13.1';
React.version = '0.13.3';
module.exports = React;
@@ -6229,7 +6239,7 @@ var ReactClass = {
("production" !== "development" ? warning(
this instanceof Constructor,
'Something is calling a React component directly. Use a factory or ' +
'JSX instead. See: http://fb.me/react-legacyfactory'
'JSX instead. See: https://fb.me/react-legacyfactory'
) : null);
}
@@ -6439,20 +6449,38 @@ ReactComponent.prototype.forceUpdate = function(callback) {
*/
if ("production" !== "development") {
var deprecatedAPIs = {
getDOMNode: 'getDOMNode',
isMounted: 'isMounted',
replaceProps: 'replaceProps',
replaceState: 'replaceState',
setProps: 'setProps'
getDOMNode: [
'getDOMNode',
'Use React.findDOMNode(component) instead.'
],
isMounted: [
'isMounted',
'Instead, make sure to clean up subscriptions and pending requests in ' +
'componentWillUnmount to prevent memory leaks.'
],
replaceProps: [
'replaceProps',
'Instead call React.render again at the top level.'
],
replaceState: [
'replaceState',
'Refactor your code to use setState instead (see ' +
'https://github.com/facebook/react/issues/3236).'
],
setProps: [
'setProps',
'Instead call React.render again at the top level.'
]
};
var defineDeprecationWarning = function(methodName, displayName) {
var defineDeprecationWarning = function(methodName, info) {
try {
Object.defineProperty(ReactComponent.prototype, methodName, {
get: function() {
("production" !== "development" ? warning(
false,
'%s(...) is deprecated in plain JavaScript React classes.',
displayName
'%s(...) is deprecated in plain JavaScript React classes. %s',
info[0],
info[1]
) : null);
return undefined;
}
@@ -6802,6 +6830,14 @@ var ReactCompositeComponentMixin = {
'Did you mean to define a state property instead?',
this.getName() || 'a component'
) : null);
("production" !== "development" ? warning(
!inst.getDefaultProps ||
inst.getDefaultProps.isReactClassApproved,
'getDefaultProps was defined on %s, a plain JavaScript class. ' +
'This is only supported for classes created using React.createClass. ' +
'Use a static property to define defaultProps instead.',
this.getName() || 'a component'
) : null);
("production" !== "development" ? warning(
!inst.propTypes,
'propTypes was defined as an instance property on %s. Use a static ' +
@@ -6838,6 +6874,7 @@ var ReactCompositeComponentMixin = {
this._pendingReplaceState = false;
this._pendingForceUpdate = false;
var childContext;
var renderedElement;
var previouslyMounting = ReactLifeCycle.currentlyMountingInstance;
@@ -6852,7 +6889,8 @@ var ReactCompositeComponentMixin = {
}
}
renderedElement = this._renderValidatedComponent();
childContext = this._getValidatedChildContext(context);
renderedElement = this._renderValidatedComponent(childContext);
} finally {
ReactLifeCycle.currentlyMountingInstance = previouslyMounting;
}
@@ -6866,7 +6904,7 @@ var ReactCompositeComponentMixin = {
this._renderedComponent,
rootID,
transaction,
this._processChildContext(context)
this._mergeChildContext(context, childContext)
);
if (inst.componentDidMount) {
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
@@ -6996,7 +7034,7 @@ var ReactCompositeComponentMixin = {
* @return {object}
* @private
*/
_processChildContext: function(currentContext) {
_getValidatedChildContext: function(currentContext) {
var inst = this._instance;
var childContext = inst.getChildContext && inst.getChildContext();
if (childContext) {
@@ -7021,6 +7059,13 @@ var ReactCompositeComponentMixin = {
name
) : invariant(name in inst.constructor.childContextTypes));
}
return childContext;
}
return null;
},
_mergeChildContext: function(currentContext, childContext) {
if (childContext) {
return assign({}, currentContext, childContext);
}
return currentContext;
@@ -7280,6 +7325,10 @@ var ReactCompositeComponentMixin = {
return inst.state;
}
if (replace && queue.length === 1) {
return queue[0];
}
var nextState = assign({}, replace ? queue[0] : inst.state);
for (var i = replace ? 1 : 0; i < queue.length; i++) {
var partial = queue[i];
@@ -7349,13 +7398,14 @@ var ReactCompositeComponentMixin = {
_updateRenderedComponent: function(transaction, context) {
var prevComponentInstance = this._renderedComponent;
var prevRenderedElement = prevComponentInstance._currentElement;
var nextRenderedElement = this._renderValidatedComponent();
var childContext = this._getValidatedChildContext();
var nextRenderedElement = this._renderValidatedComponent(childContext);
if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
ReactReconciler.receiveComponent(
prevComponentInstance,
nextRenderedElement,
transaction,
this._processChildContext(context)
this._mergeChildContext(context, childContext)
);
} else {
// These two IDs are actually the same! But nothing should rely on that.
@@ -7371,7 +7421,7 @@ var ReactCompositeComponentMixin = {
this._renderedComponent,
thisID,
transaction,
context
this._mergeChildContext(context, childContext)
);
this._replaceNodeWithMarkupByID(prevComponentID, nextMarkup);
}
@@ -7409,11 +7459,12 @@ var ReactCompositeComponentMixin = {
/**
* @private
*/
_renderValidatedComponent: function() {
_renderValidatedComponent: function(childContext) {
var renderedComponent;
var previousContext = ReactContext.current;
ReactContext.current = this._processChildContext(
this._currentElement._context
ReactContext.current = this._mergeChildContext(
this._currentElement._context,
childContext
);
ReactCurrentOwner.current = this;
try {
@@ -7778,6 +7829,7 @@ var ReactDOM = mapObject({
// SVG
circle: 'circle',
clipPath: 'clipPath',
defs: 'defs',
ellipse: 'ellipse',
g: 'g',
@@ -7927,11 +7979,13 @@ function assertValidProps(props) {
'Can only set one of `children` or `props.dangerouslySetInnerHTML`.'
) : invariant(props.children == null));
("production" !== "development" ? invariant(
props.dangerouslySetInnerHTML.__html != null,
typeof props.dangerouslySetInnerHTML === 'object' &&
'__html' in props.dangerouslySetInnerHTML,
'`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' +
'Please visit http://fb.me/react-invariant-dangerously-set-inner-html ' +
'Please visit https://fb.me/react-invariant-dangerously-set-inner-html ' +
'for more information.'
) : invariant(props.dangerouslySetInnerHTML.__html != null));
) : invariant(typeof props.dangerouslySetInnerHTML === 'object' &&
'__html' in props.dangerouslySetInnerHTML));
}
if ("production" !== "development") {
("production" !== "development" ? warning(
@@ -8239,6 +8293,8 @@ ReactDOMComponent.Mixin = {
if (propKey === STYLE) {
if (nextProp) {
nextProp = this._previousStyleCopy = assign({}, nextProp);
} else {
this._previousStyleCopy = null;
}
if (lastProp) {
// Unset styles on `lastProp` but not on `nextProp`.
@@ -10721,7 +10777,7 @@ function warnAndMonitorForKeyUse(message, element, parentType) {
("production" !== "development" ? warning(
false,
message + '%s%s See http://fb.me/react-warning-keys for more information.',
message + '%s%s See https://fb.me/react-warning-keys for more information.',
parentOrOwnerAddendum,
childOwnerAddendum
) : null);
@@ -10845,9 +10901,9 @@ function warnForPropsMutation(propName, element) {
("production" !== "development" ? warning(
false,
'Don\'t set .props.%s of the React component%s. ' +
'Instead, specify the correct value when ' +
'initially creating the element.%s',
'Don\'t set .props.%s of the React component%s. Instead, specify the ' +
'correct value when initially creating the element or use ' +
'React.cloneElement to make a new element with updated props.%s',
propName,
elementInfo,
ownerInfo
@@ -15236,6 +15292,7 @@ var ReactUpdates = _dereq_(100);
var SyntheticEvent = _dereq_(108);
var assign = _dereq_(29);
var emptyObject = _dereq_(130);
var topLevelTypes = EventConstants.topLevelTypes;
@@ -15577,6 +15634,9 @@ assign(
);
ReactShallowRenderer.prototype.render = function(element, context) {
if (!context) {
context = emptyObject;
}
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
this._render(element, transaction, context);
ReactUpdates.ReactReconcileTransaction.release(transaction);
@@ -15717,7 +15777,7 @@ for (eventType in topLevelTypes) {
module.exports = ReactTestUtils;
},{"100":100,"108":108,"16":16,"18":18,"21":21,"29":29,"31":31,"33":33,"43":43,"63":63,"65":65,"72":72,"73":73,"77":77}],96:[function(_dereq_,module,exports){
},{"100":100,"108":108,"130":130,"16":16,"18":18,"21":21,"29":29,"31":31,"33":33,"43":43,"63":63,"65":65,"72":72,"73":73,"77":77}],96:[function(_dereq_,module,exports){
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
@@ -16762,6 +16822,7 @@ var MUST_USE_ATTRIBUTE = DOMProperty.injection.MUST_USE_ATTRIBUTE;
var SVGDOMPropertyConfig = {
Properties: {
clipPath: MUST_USE_ATTRIBUTE,
cx: MUST_USE_ATTRIBUTE,
cy: MUST_USE_ATTRIBUTE,
d: MUST_USE_ATTRIBUTE,
@@ -16807,6 +16868,7 @@ var SVGDOMPropertyConfig = {
y: MUST_USE_ATTRIBUTE
},
DOMAttributeNames: {
clipPath: 'clip-path',
fillOpacity: 'fill-opacity',
fontFamily: 'font-family',
fontSize: 'font-size',
@@ -19713,6 +19775,7 @@ var shouldWrap = {
// Force wrapping for SVG elements because if they get created inside a <div>,
// they will be initialized in the wrong namespace (and will not display).
'circle': true,
'clipPath': true,
'defs': true,
'ellipse': true,
'g': true,
@@ -19755,6 +19818,7 @@ var markupWrap = {
'th': trWrap,
'circle': svgWrap,
'clipPath': svgWrap,
'defs': svgWrap,
'ellipse': svgWrap,
'g': svgWrap,
@@ -20100,6 +20164,7 @@ assign(
function isInternalComponentType(type) {
return (
typeof type === 'function' &&
typeof type.prototype !== 'undefined' &&
typeof type.prototype.mountComponent === 'function' &&
typeof type.prototype.receiveComponent === 'function'
);
@@ -21355,11 +21420,14 @@ module.exports = traverseAllChildren;
* @providesModule update
*/
/* global hasOwnProperty:true */
'use strict';
var assign = _dereq_(29);
var keyOf = _dereq_(157);
var invariant = _dereq_(150);
var hasOwnProperty = {}.hasOwnProperty;
function shallowCopy(x) {
if (Array.isArray(x)) {
@@ -21419,7 +21487,7 @@ function update(value, spec) {
COMMAND_SET
) : invariant(typeof spec === 'object'));
if (spec.hasOwnProperty(COMMAND_SET)) {
if (hasOwnProperty.call(spec, COMMAND_SET)) {
("production" !== "development" ? invariant(
Object.keys(spec).length === 1,
'Cannot have more than one key in an object with %s',
@@ -21431,7 +21499,7 @@ function update(value, spec) {
var nextValue = shallowCopy(value);
if (spec.hasOwnProperty(COMMAND_MERGE)) {
if (hasOwnProperty.call(spec, COMMAND_MERGE)) {
var mergeObj = spec[COMMAND_MERGE];
("production" !== "development" ? invariant(
mergeObj && typeof mergeObj === 'object',
@@ -21448,21 +21516,21 @@ function update(value, spec) {
assign(nextValue, spec[COMMAND_MERGE]);
}
if (spec.hasOwnProperty(COMMAND_PUSH)) {
if (hasOwnProperty.call(spec, COMMAND_PUSH)) {
invariantArrayCase(value, spec, COMMAND_PUSH);
spec[COMMAND_PUSH].forEach(function(item) {
nextValue.push(item);
});
}
if (spec.hasOwnProperty(COMMAND_UNSHIFT)) {
if (hasOwnProperty.call(spec, COMMAND_UNSHIFT)) {
invariantArrayCase(value, spec, COMMAND_UNSHIFT);
spec[COMMAND_UNSHIFT].forEach(function(item) {
nextValue.unshift(item);
});
}
if (spec.hasOwnProperty(COMMAND_SPLICE)) {
if (hasOwnProperty.call(spec, COMMAND_SPLICE)) {
("production" !== "development" ? invariant(
Array.isArray(value),
'Expected %s target to be an array; got %s',
@@ -21488,7 +21556,7 @@ function update(value, spec) {
});
}
if (spec.hasOwnProperty(COMMAND_APPLY)) {
if (hasOwnProperty.call(spec, COMMAND_APPLY)) {
("production" !== "development" ? invariant(
typeof spec[COMMAND_APPLY] === 'function',
'update(): expected spec of %s to be a function; got %s.',

18
web/static/js/react-with-addons-0.13.3.min.js поставляемый Обычный файл

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

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

@@ -52,6 +52,7 @@
<script>
window.setup_channel_page('{{ .Props.TeamDisplayName }}', '{{ .Props.TeamType }}', '{{ .Props.TeamId }}', '{{ .Props.ChannelName }}', '{{ .Props.ChannelId }}');
$('body').tooltip( {selector: '[data-toggle=tooltip]'} );
$('.modal-body').css('max-height', $(window).height() * 0.7);
$('.modal-body').perfectScrollbar();
</script>
</body>

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

@@ -18,12 +18,13 @@
<link rel="manifest" href="/static/config/manifest.json">
<!-- Android add to homescreen -->
<link rel="stylesheet" href="/static/css/bootstrap-3.3.1.min.css">
<link rel="stylesheet" href="/static/css/bootstrap-3.3.5.min.css">
<link rel="stylesheet" href="/static/css/jasny-bootstrap.min.css" rel="stylesheet">
<script src="/static/js/react-with-addons-0.13.1.js"></script>
<script src="/static/js/react-with-addons-0.13.3.js"></script>
<script src="/static/js/jquery-1.11.1.js"></script>
<script src="/static/js/bootstrap-3.3.1.js"></script>
<script src="/static/js/bootstrap-3.3.5.js"></script>
<script src="/static/js/react-bootstrap-0.25.1.js"></script>
<link id="favicon" rel="icon" href="/static/images/favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="/static/images/favicon.ico" type="image/x-icon">

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

@@ -36,9 +36,14 @@ func TearDown() {
func TestStatic(t *testing.T) {
Setup()
resp, _ := http.Get(URL + "/static/images/favicon.ico")
// add a short delay to make sure the server is ready to receive requests
time.Sleep(1 * time.Second)
if resp.StatusCode != http.StatusOK {
resp, err := http.Get(URL + "/static/images/favicon.ico")
if err != nil {
t.Fatalf("got error while trying to get static files %v", err)
} else if resp.StatusCode != http.StatusOK {
t.Fatalf("couldn't get static files %v", resp.StatusCode)
}
}