Этот коммит содержится в:
=Corey Hulen
2015-09-01 17:06:31 -07:00
родитель 72575ac7bd
Коммит f578bb1e48
18 изменённых файлов: 1003 добавлений и 459 удалений

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

@@ -3,57 +3,86 @@
var ChannelStore = require('../stores/channel_store.jsx'); var ChannelStore = require('../stores/channel_store.jsx');
module.exports = React.createClass({ export default class CommandList extends React.Component {
componentDidMount: function() { constructor(props) {
super(props);
this.state = {
channel_id: ChannelStore.getCurrentId()
};
}
componentDidMount() {
var self = this; var self = this;
if(this.refs.modal) { if (this.refs.modal) {
$(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { $(this.refs.modal.getDOMNode()).on('show.bs.modal', function show(e) {
var button = e.relatedTarget; var button = e.relatedTarget;
self.setState({ channel_id: $(button).attr('data-channelid') }); self.setState({channel_id: $(button).attr('data-channelid')});
}); });
} }
}, }
getInitialState: function() {
return { channel_id: ChannelStore.getCurrentId() }; render() {
},
render: function() {
var channel = ChannelStore.get(this.state.channel_id); var channel = ChannelStore.get(this.state.channel_id);
if (!channel) { if (!channel) {
channel = {}; channel = {};
channel.display_name = "No Channel Found"; channel.display_name = 'No Channel Found';
channel.name = "No Channel Found"; channel.name = 'No Channel Found';
channel.id = "No Channel Found"; channel.id = 'No Channel Found';
} }
return ( return (
<div className="modal fade" ref="modal" id="channel_info" tabIndex="-1" role="dialog" aria-hidden="true"> <div
<div className="modal-dialog"> className='modal fade'
<div className="modal-content"> ref='modal'
<div className="modal-header"> id='channel_info'
<button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button> tabIndex='-1'
<h4 className="modal-title" id="myModalLabel"><span className="name">{channel.display_name}</span></h4> 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'>&times;</span>
</button>
<h4
className='modal-title'
id='myModalLabel'
>
<span className='name'>{channel.display_name}</span>
</h4>
</div> </div>
<div className="modal-body"> <div className='modal-body'>
<div className="row form-group"> <div className='row form-group'>
<div className="col-sm-3 info__label">Channel Name: </div> <div className='col-sm-3 info__label'>Channel Name: </div>
<div className="col-sm-9">{channel.display_name}</div> <div className='col-sm-9'>{channel.display_name}</div>
</div> </div>
<div className="row form-group"> <div className='row form-group'>
<div className="col-sm-3 info__label">Channel Handle:</div> <div className='col-sm-3 info__label'>Channel Handle:</div>
<div className="col-sm-9">{channel.name}</div> <div className='col-sm-9'>{channel.name}</div>
</div> </div>
<div className="row"> <div className='row'>
<div className="col-sm-3 info__label">Channel ID:</div> <div className='col-sm-3 info__label'>Channel ID:</div>
<div className="col-sm-9">{channel.id}</div> <div className='col-sm-9'>{channel.id}</div>
</div> </div>
</div> </div>
<div className="modal-footer"> <div className='modal-footer'>
<button type="button" className="btn btn-default" data-dismiss="modal">Close</button> <button
type='button'
className='btn btn-default'
data-dismiss='modal'
>Close</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
); );
} }
}); }

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

@@ -9,7 +9,6 @@ var utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx'); var client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx'); var AsyncClient = require('../utils/async_client.jsx');
export default class ChannelInviteModal extends React.Component { export default class ChannelInviteModal extends React.Component {
constructor() { constructor() {
super(); super();
@@ -129,15 +128,37 @@ export default class ChannelInviteModal extends React.Component {
if (this.state.loading) { if (this.state.loading) {
content = (<LoadingScreen />); content = (<LoadingScreen />);
} else { } else {
content = (<MemberList memberList={this.state.nonmembers} isAdmin={isAdmin} handleInvite={this.handleInvite} />); content = (
<MemberList
memberList={this.state.nonmembers}
isAdmin={isAdmin}
handleInvite={this.handleInvite}
/>
);
} }
return ( return (
<div className='modal fade' id='channel_invite' tabIndex='-1' role='dialog' aria-hidden='true'> <div
<div className='modal-dialog' role='document'> className='modal fade'
id='channel_invite'
tabIndex='-1'
role='dialog'
aria-hidden='true'
>
<div
className='modal-dialog'
role='document'
>
<div className='modal-content'> <div className='modal-content'>
<div className='modal-header'> <div className='modal-header'>
<button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button> <button
type='button'
className='close'
data-dismiss='modal'
aria-label='Close'
>
<span aria-hidden='true'>&times;</span>
</button>
<h4 className='modal-title'>Add New Members to <span className='name'>{this.state.channelName}</span></h4> <h4 className='modal-title'>Add New Members to <span className='name'>{this.state.channelName}</span></h4>
</div> </div>
<div className='modal-body'> <div className='modal-body'>
@@ -145,7 +166,11 @@ export default class ChannelInviteModal extends React.Component {
{content} {content}
</div> </div>
<div className='modal-footer'> <div className='modal-footer'>
<button type='button' className='btn btn-default' data-dismiss='modal'>Close</button> <button
type='button'
className='btn btn-default'
data-dismiss='modal'
>Close</button>
</div> </div>
</div> </div>
</div> </div>
@@ -153,4 +178,3 @@ export default class ChannelInviteModal extends React.Component {
); );
} }
} }
ChannelInviteModal.displayName = 'ChannelInviteModal';

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

@@ -3,25 +3,40 @@
var client = require('../utils/client.jsx'); var client = require('../utils/client.jsx');
module.exports = React.createClass({ export default class CommandList extends React.Component {
getInitialState: function() { constructor(props) {
return { suggestions: [ ], cmd: "" }; super(props);
},
handleClick: function(i) {
this.props.addCommand(this.state.suggestions[i].suggestion)
this.setState({ suggestions: [ ], cmd: "" });
},
addFirstCommand: function() {
if (this.state.suggestions.length == 0) return;
this.handleClick(0);
},
isEmpty: function() {
return this.state.suggestions.length == 0;
},
getSuggestedCommands: function(cmd) {
if (!cmd || cmd.charAt(0) != '/') { this.handleClick = this.handleClick.bind(this);
this.setState({ suggestions: [ ], cmd: "" }); this.addFirstCommand = this.addFirstCommand.bind(this);
this.isEmpty = this.isEmpty.bind(this);
this.getSuggestedCommands = this.getSuggestedCommands.bind(this);
this.state = {
suggestions: [ ],
cmd: ''
};
}
handleClick(i) {
this.props.addCommand(this.state.suggestions[i].suggestion);
this.setState({suggestions: [ ], cmd: ''});
}
addFirstCommand() {
if (this.state.suggestions.length === 0) {
return;
}
this.handleClick(0);
}
isEmpty() {
return this.state.suggestions.length === 0;
}
getSuggestedCommands(cmd) {
if (!cmd || cmd.charAt(0) !== '/') {
this.setState({suggestions: [ ], cmd: ''});
return; return;
} }
@@ -29,36 +44,56 @@ module.exports = React.createClass({
this.props.channelId, this.props.channelId,
cmd, cmd,
true, true,
function(data) { function success(data) {
if (data.suggestions.length === 1 && data.suggestions[0].suggestion === cmd) { if (data.suggestions.length === 1 && data.suggestions[0].suggestion === cmd) {
data.suggestions = []; data.suggestions = [];
} }
this.setState({ suggestions: data.suggestions, cmd: cmd }); this.setState({suggestions: data.suggestions, cmd: cmd});
}.bind(this), }.bind(this),
function(err){ function fail() {
} }
); );
}, }
render: function() {
if (this.state.suggestions.length == 0) return (<div/>); render() {
if (this.state.suggestions.length === 0) {
return (<div/>);
}
var suggestions = []; var suggestions = [];
for (var i = 0; i < this.state.suggestions.length; i++) { for (var i = 0; i < this.state.suggestions.length; i++) {
if (this.state.suggestions[i].suggestion != this.state.cmd) { if (this.state.suggestions[i].suggestion !== this.state.cmd) {
suggestions.push( suggestions.push(
<div key={i} className="command-name" onClick={this.handleClick.bind(this, i)}> <div
<div className="command__title"><strong>{ this.state.suggestions[i].suggestion }</strong></div> key={i}
<div className="command__desc">{ this.state.suggestions[i].description }</div> className='command-name'
onClick={this.handleClick.bind(this, i)}
>
<div className='command__title'><strong>{this.state.suggestions[i].suggestion}</strong></div>
<div className='command__desc'>{this.state.suggestions[i].description}</div>
</div> </div>
); );
} }
} }
return ( return (
<div ref="mentionlist" className="command-box" style={{height:(this.state.suggestions.length*56)+2}}> <div
{ suggestions } ref='mentionlist'
className='command-box'
style={{height: (this.state.suggestions.length * 56) + 2}}
>
{suggestions}
</div> </div>
); );
} }
}); }
CommandList.defaultProps = {
channelId: null
};
CommandList.propTypes = {
addCommand: React.PropTypes.func,
channelId: React.PropTypes.string
};

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

@@ -6,20 +6,19 @@ var Constants = require('../utils/constants.jsx');
var ChannelStore = require('../stores/channel_store.jsx'); var ChannelStore = require('../stores/channel_store.jsx');
var utils = require('../utils/utils.jsx'); var utils = require('../utils/utils.jsx');
module.exports = React.createClass({ export default class FileUpload extends React.Component {
displayName: 'FileUpload', constructor(props) {
propTypes: { super(props);
onUploadError: React.PropTypes.func,
getFileCount: React.PropTypes.func, this.handleChange = this.handleChange.bind(this);
onFileUpload: React.PropTypes.func, this.handleDrop = this.handleDrop.bind(this);
onUploadStart: React.PropTypes.func,
channelId: React.PropTypes.string, this.state = {
postType: React.PropTypes.string requests: {}
}, };
getInitialState: function() { }
return {requests: {}};
}, handleChange() {
handleChange: function() {
var element = $(this.refs.fileInput.getDOMNode()); var element = $(this.refs.fileInput.getDOMNode());
var files = element.prop('files'); var files = element.prop('files');
@@ -30,7 +29,7 @@ module.exports = React.createClass({
// This looks redundant, but must be done this way due to // This looks redundant, but must be done this way due to
// setState being an asynchronous call // setState being an asynchronous call
var numFiles = 0; var numFiles = 0;
for (var i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
if (files[i].size <= Constants.MAX_FILE_SIZE) { if (files[i].size <= Constants.MAX_FILE_SIZE) {
numFiles++; numFiles++;
} }
@@ -42,7 +41,7 @@ module.exports = React.createClass({
this.props.onUploadError('Uploads limited to ' + Constants.MAX_UPLOAD_FILES + ' files maximum. Please use additional posts for more files.'); this.props.onUploadError('Uploads limited to ' + Constants.MAX_UPLOAD_FILES + ' files maximum. Please use additional posts for more files.');
} }
for (var i = 0; i < files.length && i < numToUpload; i++) { for (let i = 0; i < files.length && i < numToUpload; i++) {
if (files[i].size > Constants.MAX_FILE_SIZE) { if (files[i].size > Constants.MAX_FILE_SIZE) {
this.props.onUploadError('Files must be no more than ' + Constants.MAX_FILE_SIZE / 1000000 + ' MB'); this.props.onUploadError('Files must be no more than ' + Constants.MAX_FILE_SIZE / 1000000 + ' MB');
continue; continue;
@@ -58,7 +57,7 @@ module.exports = React.createClass({
formData.append('client_ids', clientId); formData.append('client_ids', clientId);
var request = client.uploadFile(formData, var request = client.uploadFile(formData,
function(data) { function success(data) {
var parsedData = $.parseJSON(data); var parsedData = $.parseJSON(data);
this.props.onFileUpload(parsedData.filenames, parsedData.client_ids, channelId); this.props.onFileUpload(parsedData.filenames, parsedData.client_ids, channelId);
@@ -68,7 +67,7 @@ module.exports = React.createClass({
} }
this.setState({requests: requests}); this.setState({requests: requests});
}.bind(this), }.bind(this),
function(err) { function fail(err) {
this.props.onUploadError(err, clientId); this.props.onUploadError(err, clientId);
}.bind(this) }.bind(this)
); );
@@ -87,9 +86,12 @@ module.exports = React.createClass({
element[0].type = 'text'; element[0].type = 'text';
element[0].type = 'file'; element[0].type = 'file';
} }
} catch(e) {} } catch(e) {
}, // Do nothing
handleDrop: function(e) { }
}
handleDrop(e) {
this.props.onUploadError(null); this.props.onUploadError(null);
var files = e.originalEvent.dataTransfer.files; var files = e.originalEvent.dataTransfer.files;
@@ -120,7 +122,7 @@ module.exports = React.createClass({
formData.append('client_ids', clientId); formData.append('client_ids', clientId);
var request = client.uploadFile(formData, var request = client.uploadFile(formData,
function(data) { function success(data) {
var parsedData = $.parseJSON(data); var parsedData = $.parseJSON(data);
this.props.onFileUpload(parsedData.filenames, parsedData.client_ids, channelId); this.props.onFileUpload(parsedData.filenames, parsedData.client_ids, channelId);
@@ -130,7 +132,7 @@ module.exports = React.createClass({
} }
this.setState({requests: requests}); this.setState({requests: requests});
}.bind(this), }.bind(this),
function(err) { function fail(err) {
this.props.onUploadError(err, clientId); this.props.onUploadError(err, clientId);
}.bind(this) }.bind(this)
); );
@@ -144,40 +146,41 @@ module.exports = React.createClass({
} else { } else {
this.props.onUploadError('Invalid file upload', -1); this.props.onUploadError('Invalid file upload', -1);
} }
}, }
componentDidMount: function() {
componentDidMount() {
var inputDiv = this.refs.input.getDOMNode(); var inputDiv = this.refs.input.getDOMNode();
var self = this; var self = this;
if (this.props.postType === 'post') { if (this.props.postType === 'post') {
$('.row.main').dragster({ $('.row.main').dragster({
enter: function() { enter() {
$('.center-file-overlay').removeClass('hidden'); $('.center-file-overlay').removeClass('hidden');
}, },
leave: function() { leave() {
$('.center-file-overlay').addClass('hidden'); $('.center-file-overlay').addClass('hidden');
}, },
drop: function(dragsterEvent, e) { drop(dragsterEvent, e) {
$('.center-file-overlay').addClass('hidden'); $('.center-file-overlay').addClass('hidden');
self.handleDrop(e); self.handleDrop(e);
} }
}); });
} else if (this.props.postType === 'comment') { } else if (this.props.postType === 'comment') {
$('.post-right__container').dragster({ $('.post-right__container').dragster({
enter: function() { enter() {
$('.right-file-overlay').removeClass('hidden'); $('.right-file-overlay').removeClass('hidden');
}, },
leave: function() { leave() {
$('.right-file-overlay').addClass('hidden'); $('.right-file-overlay').addClass('hidden');
}, },
drop: function(dragsterEvent, e) { drop(dragsterEvent, e) {
$('.right-file-overlay').addClass('hidden'); $('.right-file-overlay').addClass('hidden');
self.handleDrop(e); self.handleDrop(e);
} }
}); });
} }
document.addEventListener('paste', function(e) { document.addEventListener('paste', function handlePaste(e) {
var textarea = $(inputDiv.parentNode.parentNode).find('.custom-textarea')[0]; var textarea = $(inputDiv.parentNode.parentNode).find('.custom-textarea')[0];
if (textarea !== e.target && !$.contains(textarea, e.target)) { if (textarea !== e.target && !$.contains(textarea, e.target)) {
@@ -191,7 +194,7 @@ module.exports = React.createClass({
var items = e.clipboardData.items; var items = e.clipboardData.items;
var numItems = 0; var numItems = 0;
if (items) { if (items) {
for (var i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf('image') !== -1) { if (items[i].type.indexOf('image') !== -1) {
var testExt = items[i].type.split('/')[1].toLowerCase(); var testExt = items[i].type.split('/')[1].toLowerCase();
@@ -269,8 +272,9 @@ module.exports = React.createClass({
} }
} }
}); });
}, }
cancelUpload: function(clientId) {
cancelUpload(clientId) {
var requests = this.state.requests; var requests = this.state.requests;
var request = requests[clientId]; var request = requests[clientId];
@@ -280,15 +284,33 @@ module.exports = React.createClass({
delete requests[clientId]; delete requests[clientId];
this.setState({requests: requests}); this.setState({requests: requests});
} }
}, }
render: function() {
render() {
return ( return (
<span ref='input' className='btn btn-file'> <span
ref='input'
className='btn btn-file'
>
<span> <span>
<i className='glyphicon glyphicon-paperclip' /> <i className='glyphicon glyphicon-paperclip' />
</span> </span>
<input ref='fileInput' type='file' onChange={this.handleChange} multiple/> <input
ref='fileInput'
type='file'
onChange={this.handleChange}
multiple='true'
/>
</span> </span>
); );
} }
}); }
FileUpload.propTypes = {
onUploadError: React.PropTypes.func,
getFileCount: React.PropTypes.func,
onFileUpload: React.PropTypes.func,
onUploadStart: React.PropTypes.func,
channelId: React.PropTypes.string,
postType: React.PropTypes.string
};

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

@@ -1,53 +1,57 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
var utils = require('../utils/utils.jsx'); var utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx'); var client = require('../utils/client.jsx');
module.exports = React.createClass({ export default class FindTeam extends React.Component {
handleSubmit: function(e) { constructor(props) {
super(props);
this.state = {};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(e) {
e.preventDefault(); e.preventDefault();
var state = { }; var state = { };
var email = this.refs.email.getDOMNode().value.trim().toLowerCase(); var email = this.refs.email.getDOMNode().value.trim().toLowerCase();
if (!email || !utils.isEmail(email)) { if (!email || !utils.isEmail(email)) {
state.email_error = "Please enter a valid email address"; state.email_error = 'Please enter a valid email address';
this.setState(state); this.setState(state);
return; return;
} }
else {
state.email_error = ""; state.email_error = '';
}
client.findTeamsSendEmail(email, client.findTeamsSendEmail(email,
function(data) { function success() {
state.sent = true; state.sent = true;
this.setState(state); this.setState(state);
}.bind(this), }.bind(this),
function(err) { function fail(err) {
state.email_error = err.message; state.email_error = err.message;
this.setState(state); this.setState(state);
}.bind(this) }.bind(this)
); );
}, }
getInitialState: function() {
return { };
},
render: function() {
var email_error = this.state.email_error ? <label className='control-label'>{ this.state.email_error }</label> : null; render() {
var emailError = null;
var emailErrorClass = 'form-group';
var divStyle = { if (this.state.email_error) {
"marginTop": "50px", emailError = <label className='control-label'>{this.state.email_error}</label>;
emailErrorClass = 'form-group has-error';
} }
if (this.state.sent) { if (this.state.sent) {
return ( return (
<div> <div>
<h4>{"Find Your " + utils.toTitleCase(strings.Team)}</h4> <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> <p>{'An email was sent with links to any ' + strings.TeamPlural + ' to which you are a member.'}</p>
</div> </div>
); );
} }
@@ -56,17 +60,25 @@ module.exports = React.createClass({
<div> <div>
<h4>Find Your Team</h4> <h4>Find Your Team</h4>
<form onSubmit={this.handleSubmit}> <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 ' + strings.TeamPlural + ' to which you are a member.'}</p>
<div className="form-group"> <div className='form-group'>
<label className='control-label'>Email</label> <label className='control-label'>Email</label>
<div className={ email_error ? "form-group has-error" : "form-group" }> <div className={emailErrorClass}>
<input type="text" ref="email" className="form-control" placeholder="you@domain.com" maxLength="128" /> <input
{ email_error } type='text'
ref='email'
className='form-control'
placeholder='you@domain.com'
maxLength='128'
/>
{emailError}
</div> </div>
</div> </div>
<button className="btn btn-md btn-primary" type="submit">Send</button> <button
className='btn btn-md btn-primary'
type='submit'>Send</button>
</form> </form>
</div> </div>
); );
} }
}); }

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

@@ -7,10 +7,28 @@ var Client = require('../utils/client.jsx');
var UserStore = require('../stores/user_store.jsx'); var UserStore = require('../stores/user_store.jsx');
var ConfirmModal = require('./confirm_modal.jsx'); var ConfirmModal = require('./confirm_modal.jsx');
module.exports = React.createClass({ export default class InviteMemberModal extends React.Component {
componentDidMount: function() { constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.addInviteFields = this.addInviteFields.bind(this);
this.clearFields = this.clearFields.bind(this);
this.removeInviteFields = this.removeInviteFields.bind(this);
this.state = {
inviteIds: [0],
idCount: 0,
emailErrors: {},
firstNameErrors: {},
lastNameErrors: {},
emailEnabled: !ConfigStore.getSettingAsBoolean('ByPassEmail', false)
};
}
componentDidMount() {
var self = this; var self = this;
$('#invite_member').on('hide.bs.modal', function(e) { $('#invite_member').on('hide.bs.modal', function hide(e) {
if ($('#invite_member').attr('data-confirm') === 'true') { if ($('#invite_member').attr('data-confirm') === 'true') {
$('#invite_member').attr('data-confirm', 'false'); $('#invite_member').attr('data-confirm', 'false');
return; return;
@@ -31,11 +49,12 @@ module.exports = React.createClass({
} }
}); });
$('#invite_member').on('hidden.bs.modal', function() { $('#invite_member').on('hidden.bs.modal', function show() {
self.clearFields(); self.clearFields();
}); });
}, }
handleSubmit: function(e) {
handleSubmit() {
if (!this.state.emailEnabled) { if (!this.state.emailEnabled) {
return; return;
} }
@@ -90,11 +109,11 @@ module.exports = React.createClass({
data.invites = invites; data.invites = invites;
Client.inviteMembers(data, Client.inviteMembers(data,
function() { function success() {
$(this.refs.modal.getDOMNode()).attr('data-confirm', 'true'); $(this.refs.modal.getDOMNode()).attr('data-confirm', 'true');
$(this.refs.modal.getDOMNode()).modal('hide'); $(this.refs.modal.getDOMNode()).modal('hide');
}.bind(this), }.bind(this),
function(err) { function fail(err) {
if (err.message === 'This person is already on your team') { if (err.message === 'This person is already on your team') {
emailErrors[err.detailed_error] = err.message; emailErrors[err.detailed_error] = err.message;
this.setState({emailErrors: emailErrors}); this.setState({emailErrors: emailErrors});
@@ -103,18 +122,21 @@ module.exports = React.createClass({
} }
}.bind(this) }.bind(this)
); );
}, }
componentDidUpdate: function() {
componentDidUpdate() {
$(this.refs.modalBody.getDOMNode()).css('max-height', $(window).height() - 200); $(this.refs.modalBody.getDOMNode()).css('max-height', $(window).height() - 200);
$(this.refs.modalBody.getDOMNode()).css('overflow-y', 'scroll'); $(this.refs.modalBody.getDOMNode()).css('overflow-y', 'scroll');
}, }
addInviteFields: function() {
addInviteFields() {
var count = this.state.idCount + 1; var count = this.state.idCount + 1;
var inviteIds = this.state.inviteIds; var inviteIds = this.state.inviteIds;
inviteIds.push(count); inviteIds.push(count);
this.setState({inviteIds: inviteIds, idCount: count}); this.setState({inviteIds: inviteIds, idCount: count});
}, }
clearFields: function() {
clearFields() {
var inviteIds = this.state.inviteIds; var inviteIds = this.state.inviteIds;
for (var i = 0; i < inviteIds.length; i++) { for (var i = 0; i < inviteIds.length; i++) {
@@ -133,8 +155,9 @@ module.exports = React.createClass({
firstNameErrors: {}, firstNameErrors: {},
lastNameErrors: {} lastNameErrors: {}
}); });
}, }
removeInviteFields: function(index) {
removeInviteFields(index) {
var count = this.state.idCount; var count = this.state.idCount;
var inviteIds = this.state.inviteIds; var inviteIds = this.state.inviteIds;
var i = inviteIds.indexOf(index); var i = inviteIds.indexOf(index);
@@ -145,24 +168,10 @@ module.exports = React.createClass({
inviteIds.push(++count); inviteIds.push(++count);
} }
this.setState({inviteIds: inviteIds, idCount: count}); this.setState({inviteIds: inviteIds, idCount: count});
}, }
getInitialState: function() {
return {
inviteIds: [0],
idCount: 0,
emailErrors: {},
firstNameErrors: {},
lastNameErrors: {},
emailEnabled: !ConfigStore.getSettingAsBoolean('ByPassEmail', false)
};
},
render: function() {
var currentUser = UserStore.getCurrentUser();
var inputDisabled = ''; render() {
if (!this.state.emailEnabled) { var currentUser = UserStore.getCurrentUser();
inputDisabled = 'disabled';
}
if (currentUser != null) { if (currentUser != null) {
var inviteSections = []; var inviteSections = [];
@@ -185,7 +194,13 @@ module.exports = React.createClass({
var removeButton = null; var removeButton = null;
if (index) { if (index) {
removeButton = (<div> removeButton = (<div>
<button type='button' className='btn btn-link remove__member' onClick={this.removeInviteFields.bind(this, index)}><span className='fa fa-trash'></span></button> <button
type='button'
className='btn btn-link remove__member'
onClick={this.removeInviteFields.bind(this, index)}
>
<span className='fa fa-trash'></span>
</button>
</div>); </div>);
} }
var emailClass = 'form-group invite'; var emailClass = 'form-group invite';
@@ -206,13 +221,27 @@ module.exports = React.createClass({
nameFields = (<div className='row--invite'> nameFields = (<div className='row--invite'>
<div className='col-sm-6'> <div className='col-sm-6'>
<div className={firstNameClass}> <div className={firstNameClass}>
<input type='text' className='form-control' ref={'first_name' + index} placeholder='First name' maxLength='64' disabled={!this.state.emailEnabled}/> <input
type='text'
className='form-control'
ref={'first_name' + index}
placeholder='First name'
maxLength='64'
disabled={!this.state.emailEnabled}
/>
{firstNameError} {firstNameError}
</div> </div>
</div> </div>
<div className='col-sm-6'> <div className='col-sm-6'>
<div className={lastNameClass}> <div className={lastNameClass}>
<input type='text' className='form-control' ref={'last_name' + index} placeholder='Last name' maxLength='64' disabled={!this.state.emailEnabled}/> <input
type='text'
className='form-control'
ref={'last_name' + index}
placeholder='Last name'
maxLength='64'
disabled={!this.state.emailEnabled}
/>
{lastNameError} {lastNameError}
</div> </div>
</div> </div>
@@ -223,7 +252,15 @@ module.exports = React.createClass({
<div key={'key' + index}> <div key={'key' + index}>
{removeButton} {removeButton}
<div className={emailClass}> <div className={emailClass}>
<input onKeyUp={this.displayNameKeyUp} type='text' ref={'email' + index} className='form-control' placeholder='email@domain.com' maxLength='64' disabled={!this.state.emailEnabled}/> <input
onKeyUp={this.displayNameKeyUp}
type='text'
ref={'email' + index}
className='form-control'
placeholder='email@domain.com'
maxLength='64'
disabled={!this.state.emailEnabled}
/>
{emailError} {emailError}
</div> </div>
{nameFields} {nameFields}
@@ -242,23 +279,44 @@ module.exports = React.createClass({
content = ( content = (
<div> <div>
{serverError} {serverError}
<button type='button' className='btn btn-default' onClick={this.addInviteFields}>Add another</button> <button
type='button'
className='btn btn-default'
onClick={this.addInviteFields}
>Add another</button>
<br/> <br/>
<br/> <br/>
<span>People invited automatically join Town Square channel.</span> <span>People invited automatically join Town Square channel.</span>
</div> </div>
); );
sendButton = <button onClick={this.handleSubmit} type='button' className='btn btn-primary'>Send Invitations</button> sendButton =
(
<button
onClick={this.handleSubmit}
type='button'
className='btn btn-primary'
>Send Invitations</button>
);
} else { } else {
var teamInviteLink = null; var teamInviteLink = null;
if (currentUser && this.props.teamType === 'O') { if (currentUser && this.props.teamType === 'O') {
var linkUrl = utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + currentUser.team_id; var linkUrl = utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + currentUser.team_id;
var link = <a href='#' data-toggle='modal' data-target='#get_link' data-title='Team Invite' data-value={linkUrl} onClick={ var link =
function() { (
$('#invite_member').modal('hide'); <a
} href='#'
}>Team Invite Link</a>; data-toggle='modal'
data-target='#get_link'
data-title='Team Invite'
data-value={linkUrl}
onClick={
function click() {
$('#invite_member').modal('hide');
}
}
>Team Invite Link</a>
);
teamInviteLink = ( teamInviteLink = (
<p> <p>
@@ -277,22 +335,46 @@ module.exports = React.createClass({
return ( return (
<div> <div>
<div className='modal fade' ref='modal' id='invite_member' tabIndex='-1' role='dialog' aria-hidden='true'> <div
className='modal fade'
ref='modal'
id='invite_member'
tabIndex='-1'
role='dialog'
aria-hidden='true'
>
<div className='modal-dialog'> <div className='modal-dialog'>
<div className='modal-content'> <div className='modal-content'>
<div className='modal-header'> <div className='modal-header'>
<button type='button' className='close' data-dismiss='modal' aria-label='Close' data-reactid='.5.0.0.0.0'><span aria-hidden='true' data-reactid='.5.0.0.0.0.0'>×</span></button> <button
<h4 className='modal-title' id='myModalLabel'>Invite New Member</h4> type='button'
className='close'
data-dismiss='modal'
aria-label='Close'
>
<span aria-hidden='true'>×</span>
</button>
<h4
className='modal-title'
id='myModalLabel'
>Invite New Member</h4>
</div> </div>
<div ref='modalBody' className='modal-body'> <div
ref='modalBody'
className='modal-body'
>
<form role='form'> <form role='form'>
{inviteSections} {inviteSections}
</form> </form>
{content} {content}
</div> </div>
<div className='modal-footer'> <div className='modal-footer'>
<button type='button' className='btn btn-default' data-dismiss='modal'>Cancel</button> <button
{sendButton} type='button'
className='btn btn-default'
data-dismiss='modal'
>Cancel</button>
{sendButton}
</div> </div>
</div> </div>
</div> </div>
@@ -309,4 +391,8 @@ module.exports = React.createClass({
} }
return <div/>; return <div/>;
} }
}); }
InviteMemberModal.propTypes = {
teamType: React.PropTypes.string
};

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

@@ -3,32 +3,45 @@
var MemberListItem = require('./member_list_item.jsx'); var MemberListItem = require('./member_list_item.jsx');
module.exports = React.createClass({ export default class MemberList extends React.Component {
render: function() { constructor(props) {
super(props);
}
render() {
var members = []; var members = [];
if (this.props.memberList != null) { if (this.props.memberList !== null) {
members = this.props.memberList; members = this.props.memberList;
} }
var message = ""; var message = '';
if (members.length === 0) if (members.length === 0) {
message = <span>No users to add.</span>; message = <span>No users to add.</span>;
}
return ( return (
<div className="member-list-holder"> <div className='member-list-holder'>
{members.map(function(member) { {members.map(function mymembers(member) {
return <MemberListItem return (<MemberListItem
key={member.id} key={member.id}
member={member} member={member}
isAdmin={this.props.isAdmin} isAdmin={this.props.isAdmin}
handleInvite={this.props.handleInvite} handleInvite={this.props.handleInvite}
handleRemove={this.props.handleRemove} handleRemove={this.props.handleRemove}
handleMakeAdmin={this.props.handleMakeAdmin} handleMakeAdmin={this.props.handleMakeAdmin}
/>; />);
}, this)} }, this)}
{message} {message}
</div> </div>
); );
} }
}); }
MemberList.propTypes = {
memberList: React.PropTypes.array,
isAdmin: React.PropTypes.bool,
handleInvite: React.PropTypes.func,
handleRemove: React.PropTypes.func,
handleMakeAdmin: React.PropTypes.func
};

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

@@ -7,19 +7,22 @@ var Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx'); var AsyncClient = require('../utils/async_client.jsx');
var utils = require('../utils/utils.jsx'); var utils = require('../utils/utils.jsx');
module.exports = React.createClass({ export default class MoreDirectChannels extends React.Component {
displayName: 'MoreDirectChannels', constructor(props) {
componentDidMount: function() { super(props);
this.state = {channels: [], loadingDMChannel: -1};
}
componentDidMount() {
var self = this; var self = this;
$(this.refs.modal.getDOMNode()).on('show.bs.modal', function showModal(e) { $(this.refs.modal.getDOMNode()).on('show.bs.modal', function showModal(e) {
var button = e.relatedTarget; var button = e.relatedTarget;
self.setState({channels: $(button).data('channels')}); self.setState({channels: $(button).data('channels')});
}); });
}, }
getInitialState: function() {
return {channels: [], loadingDMChannel: -1}; render() {
},
render: function() {
var self = this; var self = this;
var directMessageItems = this.state.channels.map(function mapActivityToChannel(channel, index) { var directMessageItems = this.state.channels.map(function mapActivityToChannel(channel, index) {
@@ -48,7 +51,12 @@ module.exports = React.createClass({
var otherUserId = utils.getUserIdFromChannelName(channel); var otherUserId = utils.getUserIdFromChannelName(channel);
if (self.state.loadingDMChannel === index) { if (self.state.loadingDMChannel === index) {
badge = <img className='channel-loading-gif pull-right' src='/static/images/load.gif'/>; badge = (
<img
className='channel-loading-gif pull-right'
src='/static/images/load.gif'
/>
);
} }
if (self.state.loadingDMChannel === -1) { if (self.state.loadingDMChannel === -1) {
@@ -73,16 +81,36 @@ module.exports = React.createClass({
} }
return ( return (
<li key={channel.name} className={active}><a className={'sidebar-channel ' + titleClass} href='#' onClick={handleClick}>{badge}{channel.display_name}</a></li> <li
key={channel.name}
className={active}
>
<a
className={'sidebar-channel ' + titleClass}
href='#'
onClick={handleClick}
>{badge}{channel.display_name}</a>
</li>
); );
}); });
return ( return (
<div className='modal fade' id='more_direct_channels' ref='modal' tabIndex='-1' role='dialog' aria-hidden='true'> <div
className='modal fade'
id='more_direct_channels'
ref='modal'
tabIndex='-1'
role='dialog'
aria-hidden='true'
>
<div className='modal-dialog'> <div className='modal-dialog'>
<div className='modal-content'> <div className='modal-content'>
<div className='modal-header'> <div className='modal-header'>
<button type='button' className='close' data-dismiss='modal'> <button
type='button'
className='close'
data-dismiss='modal'
>
<span aria-hidden='true'>&times;</span> <span aria-hidden='true'>&times;</span>
<span className='sr-only'>Close</span> <span className='sr-only'>Close</span>
</button> </button>
@@ -94,11 +122,15 @@ module.exports = React.createClass({
</ul> </ul>
</div> </div>
<div className='modal-footer'> <div className='modal-footer'>
<button type='button' className='btn btn-default' data-dismiss='modal'>Close</button> <button
type='button'
className='btn btn-default'
data-dismiss='modal'
>Close</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
); );
} }
}); }

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

@@ -1,57 +1,70 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
var SocketStore = require('../stores/socket_store.jsx'); var SocketStore = require('../stores/socket_store.jsx');
var UserStore = require('../stores/user_store.jsx'); var UserStore = require('../stores/user_store.jsx');
module.exports = React.createClass({ export default class MsgTyping extends React.Component {
timer: null, constructor(props) {
lastTime: 0, super(props);
componentDidMount: function() {
SocketStore.addChangeListener(this._onChange);
},
componentWillReceiveProps: function(newProps) {
if(this.props.channelId !== newProps.channelId) {
this.setState({text:""});
}
},
componentWillUnmount: function() {
SocketStore.removeChangeListener(this._onChange);
},
_onChange: function(msg) {
if (msg.action == "typing" &&
this.props.channelId == msg.channel_id &&
this.props.parentId == msg.props.parent_id) {
this.timer = null;
this.lastTime = 0;
this.onChange = this.onChange.bind(this);
this.state = {
text: ''
};
}
componentDidMount() {
SocketStore.addChangeListener(this.onChange);
}
componentWillReceiveProps(newProps) {
if (this.props.channelId !== newProps.channelId) {
this.setState({text: ''});
}
}
componentWillUnmount() {
SocketStore.removeChangeListener(this.onChange);
}
onChange(msg) {
if (msg.action === 'typing' &&
this.props.channelId === msg.channel_id &&
this.props.parentId === msg.props.parent_id) {
this.lastTime = new Date().getTime(); this.lastTime = new Date().getTime();
var username = "Someone"; var username = 'Someone';
if (UserStore.hasProfile(msg.user_id)) { if (UserStore.hasProfile(msg.user_id)) {
username = UserStore.getProfile(msg.user_id).username; username = UserStore.getProfile(msg.user_id).username;
} }
this.setState({ text: username + " is typing..." }); this.setState({text: username + ' is typing...'});
if (!this.timer) { if (!this.timer) {
var outer = this; this.timer = setInterval(function myTimer() {
outer.timer = setInterval(function() { if ((new Date().getTime() - this.lastTime) > 8000) {
if ((new Date().getTime() - outer.lastTime) > 8000) { this.setState({text: ''});
outer.setState({ text: "" });
} }
}, 3000); }.bind(this), 3000);
} }
} else if (msg.action === 'posted' && msg.channel_id === this.props.channelId) {
this.setState({text: ''});
} }
else if (msg.action == "posted" && msg.channel_id === this.props.channelId) { }
this.setState({text:""})
} render() {
},
getInitialState: function() {
return { text: "" };
},
render: function() {
return ( return (
<span className="msg-typing">{ this.state.text }</span> <span className='msg-typing'>{this.state.text}</span>
); );
} }
}); }
MsgTyping.propTypes = {
channelId: React.PropTypes.string,
parentId: React.PropTypes.string
};

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

@@ -51,8 +51,10 @@ export default class PostList extends React.Component {
if (deletedPosts && Object.keys(deletedPosts).length > 0) { if (deletedPosts && Object.keys(deletedPosts).length > 0) {
for (var pid in deletedPosts) { for (var pid in deletedPosts) {
postList.posts[pid] = deletedPosts[pid]; if (deletedPosts.hasOwnProperty(pid)) {
postList.order.unshift(pid); postList.posts[pid] = deletedPosts[pid];
postList.order.unshift(pid);
}
} }
postList.order.sort(function postSort(a, b) { postList.order.sort(function postSort(a, b) {
@@ -71,7 +73,9 @@ export default class PostList extends React.Component {
if (pendingPostList) { if (pendingPostList) {
postList.order = pendingPostList.order.concat(postList.order); postList.order = pendingPostList.order.concat(postList.order);
for (var ppid in pendingPostList.posts) { for (var ppid in pendingPostList.posts) {
postList.posts[ppid] = pendingPostList.posts[ppid]; if (pendingPostList.posts.hasOwnProperty(ppid)) {
postList.posts[ppid] = pendingPostList.posts[ppid];
}
} }
} }
} }
@@ -267,7 +271,6 @@ export default class PostList extends React.Component {
} }
} }
onSocketChange(msg) { onSocketChange(msg) {
var postList;
var post; var post;
if (msg.action === 'posted' || msg.action === 'post_edited') { if (msg.action === 'posted' || msg.action === 'post_edited') {
post = JSON.parse(msg.props.post); post = JSON.parse(msg.props.post);
@@ -280,7 +283,6 @@ export default class PostList extends React.Component {
} }
post = JSON.parse(msg.props.post); post = JSON.parse(msg.props.post);
postList = this.state.postList;
PostStore.storeUnseenDeletedPost(post); PostStore.storeUnseenDeletedPost(post);
PostStore.removePost(post, true); PostStore.removePost(post, true);
@@ -644,11 +646,18 @@ export default class PostList extends React.Component {
if (posts && this.state.isFirstLoadComplete) { if (posts && this.state.isFirstLoadComplete) {
postCtls = this.createPosts(posts, order); postCtls = this.createPosts(posts, order);
} else { } else {
postCtls.push(<LoadingScreen position='absolute' />); postCtls.push(
<LoadingScreen
position='absolute'
key='loading'
/>);
} }
return ( return (
<div ref='postlist' className='post-list-holder-by-time'> <div
ref='postlist'
className='post-list-holder-by-time'
>
<div className='post-list__table'> <div className='post-list__table'>
<div className='post-list__content'> <div className='post-list__content'>
{moreMessages} {moreMessages}

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

@@ -3,62 +3,99 @@
var ChannelStore = require('../stores/channel_store.jsx'); var ChannelStore = require('../stores/channel_store.jsx');
var UserStore = require('../stores/user_store.jsx'); var UserStore = require('../stores/user_store.jsx');
var BrowserStore = require('../stores/browser_store.jsx') var BrowserStore = require('../stores/browser_store.jsx');
var utils = require('../utils/utils.jsx'); var utils = require('../utils/utils.jsx');
module.exports = React.createClass({ export default class RemovedFromChannelModal extends React.Component {
handleShow: function() { constructor(props) {
var newState = {}; super(props);
if(BrowserStore.getItem("channel-removed-state")) {
newState = BrowserStore.getItem("channel-removed-state");
BrowserStore.removeItem("channel-removed-state");
}
this.setState(newState); this.handleShow = this.handleShow.bind(this);
}, this.handleClose = this.handleClose.bind(this);
handleClose: function() {
var townSquare = ChannelStore.getByName("town-square");
utils.switchChannel(townSquare);
this.setState({channelName: "", remover: ""}); this.state = {
}, channelName: '',
componentDidMount: function() { remover: ''
$(this.getDOMNode()).on('show.bs.modal',this.handleShow); };
$(this.getDOMNode()).on('hidden.bs.modal',this.handleClose); }
},
componentWillUnmount: function() { handleShow() {
$(this.getDOMNode()).off('show.bs.modal',this.handleShow); var newState = {};
$(this.getDOMNode()).off('hidden.bs.modal',this.handleClose); if (BrowserStore.getItem('channel-removed-state')) {
}, newState = BrowserStore.getItem('channel-removed-state');
getInitialState: function() { BrowserStore.removeItem('channel-removed-state');
return {channelName: "", remover: ""} }
},
render: function() { this.setState(newState);
}
handleClose() {
var townSquare = ChannelStore.getByName('town-square');
utils.switchChannel(townSquare);
this.setState({channelName: '', remover: ''});
}
componentDidMount() {
$(React.findDOMNode(this)).on('show.bs.modal', this.handleShow);
$(React.findDOMNode(this)).on('hidden.bs.modal', this.handleClose);
}
componentWillUnmount() {
$(React.findDOMNode(this)).off('show.bs.modal', this.handleShow);
$(React.findDOMNode(this)).off('hidden.bs.modal', this.handleClose);
}
render() {
var currentUser = UserStore.getCurrentUser(); var currentUser = UserStore.getCurrentUser();
var channelName = this.state.channelName ? this.state.channelName : "the channel"
var remover = this.state.remover ? this.state.remover : "Someone" var channelName = 'the channel';
if (this.state.channelName) {
channelName = this.state.channelName;
}
var remover = 'Someone';
if (this.state.remover) {
remover = this.state.remover;
}
if (currentUser != null) { if (currentUser != null) {
return ( return (
<div className='modal fade' ref='modal' id='removed_from_channel' tabIndex='-1' role='dialog' aria-hidden='true'> <div
<div className='modal-dialog'> className='modal fade'
<div className='modal-content'> ref='modal'
<div className='modal-header'> id='removed_from_channel'
<button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button> tabIndex='-1'
<h4 className='modal-title'>Removed from <span className='name'>{channelName}</span></h4> 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'>&times;</span></button>
<h4 className='modal-title'>Removed from <span className='name'>{channelName}</span></h4>
</div>
<div className='modal-body'>
<p>{remover} removed you from {channelName}</p>
</div>
<div className='modal-footer'>
<button
type='button'
className='btn btn-primary'
data-dismiss='modal'
>Okay</button>
</div>
</div> </div>
<div className='modal-body'> </div>
<p>{remover} removed you from {channelName}</p>
</div>
<div className='modal-footer'>
<button type='button' className='btn btn-primary' data-dismiss='modal'>Okay</button>
</div>
</div>
</div>
</div> </div>
); );
} else {
return <div/>;
} }
return <div/>;
} }
}); }

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

@@ -33,7 +33,9 @@ export default class RhsThread extends React.Component {
if (pendingPostList) { if (pendingPostList) {
for (var pid in pendingPostList.posts) { for (var pid in pendingPostList.posts) {
postList.posts[pid] = pendingPostList.posts[pid]; if (pendingPostList.posts.hasOwnProperty(pid)) {
postList.posts[pid] = pendingPostList.posts[pid];
}
} }
} }
@@ -81,7 +83,9 @@ export default class RhsThread extends React.Component {
if (currentPosts.posts[currentPosts.order[0]].channel_id === currentSelected.posts[currentSelected.order[0]].channel_id) { if (currentPosts.posts[currentPosts.order[0]].channel_id === currentSelected.posts[currentSelected.order[0]].channel_id) {
currentSelected.posts = {}; currentSelected.posts = {};
for (var postId in currentPosts.posts) { for (var postId in currentPosts.posts) {
currentSelected.posts[postId] = currentPosts.posts[postId]; if (currentPosts.posts.hasOwnProperty(postId)) {
currentSelected.posts[postId] = currentPosts.posts[postId];
}
} }
PostStore.storeSelectedPost(currentSelected); PostStore.storeSelectedPost(currentSelected);
@@ -128,9 +132,11 @@ export default class RhsThread extends React.Component {
var postsArray = []; var postsArray = [];
for (var postId in postList.posts) { for (var postId in postList.posts) {
var cpost = postList.posts[postId]; if (postList.posts.hasOwnProperty(postId)) {
if (cpost.root_id === rootPost.id) { var cpost = postList.posts[postId];
postsArray.push(cpost); if (cpost.root_id === rootPost.id) {
postsArray.push(cpost);
}
} }
} }
@@ -209,6 +215,7 @@ RhsThread.defaultProps = {
fromSearch: '', fromSearch: '',
isMentionSearch: false isMentionSearch: false
}; };
RhsThread.propTypes = { RhsThread.propTypes = {
fromSearch: React.PropTypes.string, fromSearch: React.PropTypes.string,
isMentionSearch: React.PropTypes.bool isMentionSearch: React.PropTypes.bool

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

@@ -1,25 +1,33 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
module.exports = React.createClass({ export default class SettingPicture extends React.Component {
setPicture: function(file) { constructor(props) {
super(props);
this.setPicture = this.setPicture.bind(this);
}
setPicture(file) {
if (file) { if (file) {
var reader = new FileReader(); var reader = new FileReader();
var img = this.refs.image.getDOMNode(); var img = this.refs.image.getDOMNode();
reader.onload = function(e) { reader.onload = function load(e) {
$(img).attr('src', e.target.result); $(img).attr('src', e.target.result);
}; };
reader.readAsDataURL(file); reader.readAsDataURL(file);
} }
}, }
componentWillReceiveProps: function(nextProps) {
componentWillReceiveProps(nextProps) {
if (nextProps.picture) { if (nextProps.picture) {
this.setPicture(nextProps.picture); this.setPicture(nextProps.picture);
} }
}, }
render: function() {
render() {
var clientError = null; var clientError = null;
if (this.props.client_error) { if (this.props.client_error) {
clientError = <div className='form-group has-error'><label className='control-label'>{this.props.client_error}</label></div>; clientError = <div className='form-group has-error'><label className='control-label'>{this.props.client_error}</label></div>;
@@ -31,14 +39,31 @@ module.exports = React.createClass({
var img = null; var img = null;
if (this.props.picture) { if (this.props.picture) {
img = (<img ref='image' className='profile-img' src=''/>); img = (
<img
ref='image'
className='profile-img'
src=''
/>
);
} else { } else {
img = (<img ref='image' className='profile-img' src={this.props.src}/>); img = (
<img
ref='image'
className='profile-img'
src={this.props.src}
/>
);
} }
var confirmButton; var confirmButton;
if (this.props.loadingPicture) { if (this.props.loadingPicture) {
confirmButton = <img className='spinner' src='/static/images/load.gif'/>; confirmButton = (
<img
className='spinner'
src='/static/images/load.gif'
/>
);
} else { } else {
var confirmButtonClass = 'btn btn-sm'; var confirmButtonClass = 'btn btn-sm';
if (this.props.submitActive) { if (this.props.submitActive) {
@@ -46,9 +71,15 @@ module.exports = React.createClass({
} else { } else {
confirmButtonClass += ' btn-inactive disabled'; confirmButtonClass += ' btn-inactive disabled';
} }
confirmButton = <a className={confirmButtonClass} onClick={this.props.submit}>Save</a>;
confirmButton = (
<a
className={confirmButtonClass}
onClick={this.props.submit}
>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 ' + config.ProfileWidth + 'px in width and ' + config.ProfileHeight + 'px height.';
var self = this; var self = this;
return ( return (
@@ -65,13 +96,36 @@ module.exports = React.createClass({
<li className='setting-list-item'> <li className='setting-list-item'>
{serverError} {serverError}
{clientError} {clientError}
<span className='btn btn-sm btn-primary btn-file sel-btn'>Select<input ref='input' accept='.jpg,.png,.bmp' type='file' onChange={this.props.pictureChange}/></span> <span className='btn btn-sm btn-primary btn-file sel-btn'
>Select<input
ref='input'
accept='.jpg,.png,.bmp'
type='file'
onChange={this.props.pictureChange}
/>
</span>
{confirmButton} {confirmButton}
<a className='btn btn-sm theme' href='#' onClick={self.props.updateSection}>Cancel</a> <a
className='btn btn-sm theme'
href='#'
onClick={self.props.updateSection}
>Cancel</a>
</li> </li>
</ul> </ul>
</li> </li>
</ul> </ul>
); );
} }
}); }
SettingPicture.propTypes = {
client_error: React.PropTypes.string,
server_error: React.PropTypes.string,
src: React.PropTypes.string,
picture: React.PropTypes.object,
loadingPicture: React.PropTypes.bool,
submitActive: React.PropTypes.bool,
submit: React.PropTypes.func,
title: React.PropTypes.string,
pictureChange: React.PropTypes.func
};

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

@@ -1,36 +1,37 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
module.exports = React.createClass({ export default class SettingsUpload extends React.Component {
displayName: 'Setting Upload',
propTypes: { constructor(props) {
title: React.PropTypes.string.isRequired, super(props);
submit: React.PropTypes.func.isRequired,
fileTypesAccepted: React.PropTypes.string.isRequired, this.doFileSelect = this.doFileSelect.bind(this);
clientError: React.PropTypes.string, this.doSubmit = this.doSubmit.bind(this);
serverError: React.PropTypes.string, this.onFileSelect = this.onFileSelect.bind(this);
helpText: React.PropTypes.string
}, this.state = {
getInitialState: function() {
return {
clientError: this.props.clientError, clientError: this.props.clientError,
serverError: this.props.serverError serverError: this.props.serverError
}; };
}, }
componentWillReceiveProps: function() {
componentWillReceiveProps() {
this.setState({ this.setState({
clientError: this.props.clientError, clientError: this.props.clientError,
serverError: this.props.serverError serverError: this.props.serverError
}); });
}, }
doFileSelect: function(e) {
doFileSelect(e) {
e.preventDefault(); e.preventDefault();
this.setState({ this.setState({
clientError: '', clientError: '',
serverError: '' serverError: ''
}); });
}, }
doSubmit: function(e) {
doSubmit(e) {
e.preventDefault(); e.preventDefault();
var inputnode = this.refs.uploadinput.getDOMNode(); var inputnode = this.refs.uploadinput.getDOMNode();
if (inputnode.files && inputnode.files[0]) { if (inputnode.files && inputnode.files[0]) {
@@ -38,16 +39,18 @@ module.exports = React.createClass({
} else { } else {
this.setState({clientError: 'No file selected.'}); this.setState({clientError: 'No file selected.'});
} }
}, }
onFileSelect: function(e) {
onFileSelect(e) {
var filename = $(e.target).val(); var filename = $(e.target).val();
if (filename.substring(3, 11) === 'fakepath') { if (filename.substring(3, 11) === 'fakepath') {
filename = filename.substring(12); filename = filename.substring(12);
} }
$(e.target).closest('li').find('.file-status').addClass('hide'); $(e.target).closest('li').find('.file-status').addClass('hide');
$(e.target).closest('li').find('.file-name').removeClass('hide').html(filename); $(e.target).closest('li').find('.file-name').removeClass('hide').html(filename);
}, }
render: function() {
render() {
var clientError = null; var clientError = null;
if (this.state.clientError) { if (this.state.clientError) {
clientError = ( clientError = (
@@ -67,7 +70,11 @@ module.exports = React.createClass({
<li className='col-xs-offset-3 col-xs-8'> <li className='col-xs-offset-3 col-xs-8'>
<ul className='setting-list'> <ul className='setting-list'>
<li className='setting-list-item'> <li className='setting-list-item'>
<span className='btn btn-sm btn-primary btn-file sel-btn'>Select file<input ref='uploadinput' accept={this.props.fileTypesAccepted} type='file' onChange={this.onFileSelect}/></span> <span className='btn btn-sm btn-primary btn-file sel-btn'>Select file<input
ref='uploadinput'
accept={this.props.fileTypesAccepted}
type='file'
onChange={this.onFileSelect}/></span>
<a <a
className={'btn btn-sm btn-primary'} className={'btn btn-sm btn-primary'}
onClick={this.doSubmit}> onClick={this.doSubmit}>
@@ -82,4 +89,13 @@ module.exports = React.createClass({
</ul> </ul>
); );
} }
}); }
SettingsUpload.propTypes = {
title: React.PropTypes.string.isRequired,
submit: React.PropTypes.func.isRequired,
fileTypesAccepted: React.PropTypes.string.isRequired,
clientError: React.PropTypes.string,
serverError: React.PropTypes.string,
helpText: React.PropTypes.object
};

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

@@ -5,12 +5,19 @@ var UserStore = require('../stores/user_store.jsx');
var client = require('../utils/client.jsx'); var client = require('../utils/client.jsx');
var utils = require('../utils/utils.jsx'); var utils = require('../utils/utils.jsx');
module.exports = React.createClass({ export default class SidebarRightMenu extends React.Component {
handleLogoutClick: function(e) { constructor(props) {
super(props);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
}
handleLogoutClick(e) {
e.preventDefault(); e.preventDefault();
client.logout(); client.logout();
}, }
render: function() {
render() {
var teamLink = ''; var teamLink = '';
var inviteLink = ''; var inviteLink = '';
var teamSettingsLink = ''; var teamSettingsLink = '';
@@ -23,14 +30,22 @@ module.exports = React.createClass({
inviteLink = ( inviteLink = (
<li> <li>
<a href='#' data-toggle='modal' data-target='#invite_member'><i className='glyphicon glyphicon-user'></i>Invite New Member</a> <a href='#'
data-toggle='modal'
data-target='#invite_member'
><i className='glyphicon glyphicon-user'></i>Invite New Member</a>
</li> </li>
); );
if (this.props.teamType === 'O') { if (this.props.teamType === 'O') {
teamLink = ( teamLink = (
<li> <li>
<a href='#' data-toggle='modal' data-target='#get_link' data-title='Team Invite' data-value={utils.getWindowLocationOrigin()+'/signup_user_complete/?id='+currentUser.team_id}><i className='glyphicon glyphicon-link'></i>Get Team Invite Link</a> <a href='#'
data-toggle='modal'
data-target='#get_link'
data-title='Team Invite'
data-value={utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + currentUser.team_id}
><i className='glyphicon glyphicon-link'></i>Get Team Invite Link</a>
</li> </li>
); );
} }
@@ -39,12 +54,20 @@ module.exports = React.createClass({
if (isAdmin) { if (isAdmin) {
teamSettingsLink = ( teamSettingsLink = (
<li> <li>
<a href='#' data-toggle='modal' data-target='#team_settings'><i className='glyphicon glyphicon-globe'></i>Team Settings</a> <a
href='#'
data-toggle='modal'
data-target='#team_settings'
><i className='glyphicon glyphicon-globe'></i>Team Settings</a>
</li> </li>
); );
manageLink = ( manageLink = (
<li> <li>
<a href='#' data-toggle='modal' data-target='#team_members'><i className='glyphicon glyphicon-wrench'></i>Manage Team</a> <a
href='#'
data-toggle='modal'
data-target='#team_members'
><i className='glyphicon glyphicon-wrench'></i>Manage Team</a>
</li> </li>
); );
} }
@@ -61,23 +84,48 @@ module.exports = React.createClass({
return ( return (
<div> <div>
<div className='team__header theme'> <div className='team__header theme'>
<a className='team__name' href='/channels/town-square'>{teamDisplayName}</a> <a
className='team__name'
href='/channels/town-square'
>{teamDisplayName}</a>
</div> </div>
<div className='nav-pills__container'> <div className='nav-pills__container'>
<ul className='nav nav-pills nav-stacked'> <ul className='nav nav-pills nav-stacked'>
<li><a href='#' data-toggle='modal' data-target='#user_settings'><i className='glyphicon glyphicon-cog'></i>Account Settings</a></li> <li>
<a
href='#'
data-toggle='modal'
data-target='#user_settings'
><i className='glyphicon glyphicon-cog'></i>Account Settings</a></li>
{teamSettingsLink} {teamSettingsLink}
{inviteLink} {inviteLink}
{teamLink} {teamLink}
{manageLink} {manageLink}
<li><a href='#' onClick={this.handleLogoutClick}><i className='glyphicon glyphicon-log-out'></i>Logout</a></li> <li>
<a
href='#'
onClick={this.handleLogoutClick}
><i className='glyphicon glyphicon-log-out'></i>Logout</a></li>
<li className='divider'></li> <li className='divider'></li>
<li><a target='_blank' href='/static/help/configure_links.html'><i className='glyphicon glyphicon-question-sign'></i>Help</a></li> <li>
<li><a target='_blank' href='/static/help/configure_links.html'><i className='glyphicon glyphicon-earphone'></i>Report a Problem</a></li> <a
target='_blank'
href='/static/help/configure_links.html'
><i className='glyphicon glyphicon-question-sign'></i>Help</a></li>
<li>
<a
target='_blank'
href='/static/help/configure_links.html'
><i className='glyphicon glyphicon-earphone'></i>Report a Problem</a></li>
</ul> </ul>
</div> </div>
</div> </div>
); );
} }
}); }
SidebarRightMenu.propTypes = {
teamType: React.PropTypes.string,
teamDisplayName: React.PropTypes.string
};

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

@@ -4,22 +4,34 @@
var utils = require('../utils/utils.jsx'); var utils = require('../utils/utils.jsx');
var SettingUpload = require('./setting_upload.jsx'); var SettingUpload = require('./setting_upload.jsx');
module.exports = React.createClass({ export default class TeamImportTab extends React.Component {
displayName: 'Import Tab', constructor(props) {
getInitialState: function() { super(props);
return {status: 'ready', link: ''};
}, this.onImportFailure = this.onImportFailure.bind(this);
onImportFailure: function() { this.onImportSuccess = this.onImportSuccess.bind(this);
this.doImportSlack = this.doImportSlack.bind(this);
this.state = {
status: 'ready',
link: ''
};
}
onImportFailure() {
this.setState({status: 'fail', link: ''}); this.setState({status: 'fail', link: ''});
}, }
onImportSuccess: function(data) {
onImportSuccess(data) {
this.setState({status: 'done', link: 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(data)}); this.setState({status: 'done', link: 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(data)});
}, }
doImportSlack: function(file) {
doImportSlack(file) {
this.setState({status: 'in-progress', link: ''}); this.setState({status: 'in-progress', link: ''});
utils.importSlack(file, this.onImportSuccess, this.onImportFailure); utils.importSlack(file, this.onImportSuccess, this.onImportFailure);
}, }
render: function() {
render() {
var uploadHelpText = ( var uploadHelpText = (
<div> <div>
<br/> <br/>
@@ -39,22 +51,25 @@ module.exports = React.createClass({
var messageSection; var messageSection;
switch (this.state.status) { switch (this.state.status) {
case 'ready':
messageSection = ''; case 'ready':
messageSection = '';
break; break;
case 'in-progress': case 'in-progress':
messageSection = ( messageSection = (
<p className="confirm-import alert alert-warning"><i className="fa fa-spinner fa-pulse"></i> Importing...</p> <p className='confirm-import alert alert-warning'><i className='fa fa-spinner fa-pulse'></i> Importing...</p>
); );
break; break;
case 'done': case 'done':
messageSection = ( messageSection = (
<p className="confirm-import alert alert-success"><i className="fa fa-check"></i> Import successful: <a href={this.state.link} download='MattermostImportSummary.txt'>View Summary</a></p> <p className='confirm-import alert alert-success'><i className='fa fa-check'></i> Import successful: <a href={this.state.link}
download='MattermostImportSummary.txt'>View Summary</a></p>
); );
break; break;
case 'fail': case 'fail':
messageSection = ( messageSection = (
<p className="confirm-import alert alert-warning"><i className="fa fa-warning"></i> Import failure: <a href={this.state.link} download='MattermostImportSummary.txt'>View Summary</a></p> <p className='confirm-import alert alert-warning'><i className='fa fa-warning'></i> Import failure: <a href={this.state.link}
download='MattermostImportSummary.txt'>View Summary</a></p>
); );
break; break;
} }
@@ -62,10 +77,22 @@ module.exports = React.createClass({
return ( return (
<div> <div>
<div className='modal-header'> <div className='modal-header'>
<button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button> <button type='button'
<h4 className='modal-title' ref='title'><i className='modal-back'></i>Import</h4> className='close'
data-dismiss='modal'
aria-label='Close'
>
<span aria-hidden='true'>&times;</span>
</button>
<h4
className='modal-title'
ref='title'
><i className='modal-back'></i>Import</h4>
</div> </div>
<div ref='wrapper' className='user-settings'> <div
ref='wrapper'
className='user-settings'
>
<h3 className='tab-header'>Import</h3> <h3 className='tab-header'>Import</h3>
<div className='divider-dark first'/> <div className='divider-dark first'/>
{uploadSection} {uploadSection}
@@ -75,4 +102,4 @@ module.exports = React.createClass({
</div> </div>
); );
} }
}); }

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

@@ -2,77 +2,131 @@
// See License.txt for license information. // See License.txt for license information.
var UserStore = require('../stores/user_store.jsx'); var UserStore = require('../stores/user_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
var MemberListTeam = require('./member_list_team.jsx'); var MemberListTeam = require('./member_list_team.jsx');
var Client = require('../utils/client.jsx');
var utils = require('../utils/utils.jsx'); var utils = require('../utils/utils.jsx');
function getStateFromStores() { function getStateFromStores() {
var users = UserStore.getProfiles(); var users = UserStore.getProfiles();
var member_list = []; var memberList = [];
for (var id in users) member_list.push(users[id]); for (var id in users) {
if (users.hasOwnProperty(id)) {
memberList.push(users[id]);
}
}
memberList.sort(function sort(a, b) {
if (a.username < b.username) {
return -1;
}
if (a.username > b.username) {
return 1;
}
member_list.sort(function(a,b) {
if (a.username < b.username) return -1;
if (a.username > b.username) return 1;
return 0; return 0;
}); });
return { return {
member_list: member_list member_list: memberList
}; };
} }
module.exports = React.createClass({ export default class TeamMembers extends React.Component {
componentDidMount: function() { constructor(props) {
UserStore.addChangeListener(this._onChange); super(props);
this.onChange = this.onChange.bind(this);
this.state = getStateFromStores();
}
componentDidMount() {
UserStore.addChangeListener(this.onChange);
var self = this; var self = this;
$(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) { $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function show() {
self.setState({ render_members: false }); self.setState({render_members: false});
}); });
$(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { $(this.refs.modal.getDOMNode()).on('show.bs.modal', function hide() {
self.setState({ render_members: true }); self.setState({render_members: true});
}); });
}, }
componentWillUnmount: function() {
UserStore.removeChangeListener(this._onChange); componentWillUnmount() {
}, UserStore.removeChangeListener(this.onChange);
_onChange: function() { }
onChange() {
var newState = getStateFromStores(); var newState = getStateFromStores();
if (!utils.areStatesEqual(newState, this.state)) { if (!utils.areStatesEqual(newState, this.state)) {
this.setState(newState); this.setState(newState);
} }
}, }
getInitialState: function() {
return getStateFromStores(); render() {
}, var serverError = null;
render: function() {
var server_error = this.state.server_error ? <label className='has-error control-label'>{this.state.server_error}</label> : null; if (this.state.server_error) {
serverError = <label className='has-error control-label'>{this.state.server_error}</label>;
}
var renderMembers = '';
if (this.state.render_members) {
renderMembers = <MemberListTeam users={this.state.member_list} />;
}
return ( return (
<div className="modal fade" ref="modal" id="team_members" tabIndex="-1" role="dialog" aria-hidden="true"> <div
<div className="modal-dialog"> className='modal fade'
<div className="modal-content"> ref='modal'
<div className="modal-header"> id='team_members'
<button type="button" className="close" data-dismiss="modal" aria-label="Close" data-reactid=".5.0.0.0.0"><span aria-hidden="true" data-reactid=".5.0.0.0.0.0">×</span></button> tabIndex='-1'
<h4 className="modal-title" id="myModalLabel">{this.props.teamDisplayName + " Members"}</h4> role='dialog'
</div> aria-hidden='true'
<div ref="modalBody" className="modal-body"> >
<div className="channel-settings"> <div className='modal-dialog'>
<div className="team-member-list"> <div className='modal-content'>
{ this.state.render_members ? <MemberListTeam users={this.state.member_list} /> : "" } <div className='modal-header'>
<button
type='button'
className='close'
data-dismiss='modal'
aria-label='Close'
>
<span aria-hidden='true'>×</span>
</button>
<h4
className='modal-title'
id='myModalLabel'
>{this.props.teamDisplayName + ' Members'}</h4>
</div>
<div
ref='modalBody'
className='modal-body'
>
<div className='channel-settings'>
<div className='team-member-list'>
{renderMembers}
</div>
{serverError}
</div> </div>
{ server_error } </div>
<div className='modal-footer'>
<button
type='button'
className='btn btn-default'
data-dismiss='modal'
>Close</button>
</div> </div>
</div> </div>
<div className="modal-footer">
<button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div> </div>
</div> </div>
); );
} }
}); }
TeamMembers.propTypes = {
teamDisplayName: React.PropTypes.string
};

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

@@ -8,56 +8,82 @@ var SecurityTab = require('./user_settings_security.jsx');
var GeneralTab = require('./user_settings_general.jsx'); var GeneralTab = require('./user_settings_general.jsx');
var AppearanceTab = require('./user_settings_appearance.jsx'); var AppearanceTab = require('./user_settings_appearance.jsx');
module.exports = React.createClass({ export default class UserSettings extends React.Component {
displayName: 'UserSettings', constructor(props) {
propTypes: { super(props);
activeTab: React.PropTypes.string,
activeSection: React.PropTypes.string, this.onListenerChange = this.onListenerChange.bind(this);
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func this.state = {user: UserStore.getCurrentUser()};
}, }
componentDidMount: function() {
componentDidMount() {
UserStore.addChangeListener(this.onListenerChange); UserStore.addChangeListener(this.onListenerChange);
}, }
componentWillUnmount: function() {
componentWillUnmount() {
UserStore.removeChangeListener(this.onListenerChange); UserStore.removeChangeListener(this.onListenerChange);
}, }
onListenerChange: function () {
onListenerChange() {
var user = UserStore.getCurrentUser(); var user = UserStore.getCurrentUser();
if (!utils.areStatesEqual(this.state.user, user)) { if (!utils.areStatesEqual(this.state.user, user)) {
this.setState({user: user}); this.setState({user: user});
} }
}, }
getInitialState: function() {
return {user: UserStore.getCurrentUser()}; render() {
},
render: function() {
if (this.props.activeTab === 'general') { if (this.props.activeTab === 'general') {
return ( return (
<div> <div>
<GeneralTab user={this.state.user} activeSection={this.props.activeSection} updateSection={this.props.updateSection} /> <GeneralTab
user={this.state.user}
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
/>
</div> </div>
); );
} else if (this.props.activeTab === 'security') { } else if (this.props.activeTab === 'security') {
return ( return (
<div> <div>
<SecurityTab user={this.state.user} activeSection={this.props.activeSection} updateSection={this.props.updateSection} updateTab={this.props.updateTab} /> <SecurityTab
user={this.state.user}
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
updateTab={this.props.updateTab}
/>
</div> </div>
); );
} else if (this.props.activeTab === 'notifications') { } else if (this.props.activeTab === 'notifications') {
return ( return (
<div> <div>
<NotificationsTab user={this.state.user} activeSection={this.props.activeSection} updateSection={this.props.updateSection} updateTab={this.props.updateTab} /> <NotificationsTab
user={this.state.user}
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
updateTab={this.props.updateTab}
/>
</div> </div>
); );
} else if (this.props.activeTab === 'appearance') { } else if (this.props.activeTab === 'appearance') {
return ( return (
<div> <div>
<AppearanceTab activeSection={this.props.activeSection} updateSection={this.props.updateSection} updateTab={this.props.updateTab} /> <AppearanceTab
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
updateTab={this.props.updateTab}
/>
</div> </div>
); );
} else {
return <div/>;
} }
return <div/>;
} }
}); }
UserSettings.propTypes = {
activeTab: React.PropTypes.string,
activeSection: React.PropTypes.string,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func
};