Этот коммит содержится в:
nickago
2015-08-31 10:10:22 -07:00
родитель 72575ac7bd
Коммит 069f851313
28 изменённых файлов: 1487 добавлений и 986 удалений

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

@@ -3,13 +3,21 @@
var Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var Constants = require('../utils/constants.jsx');
var utils = require('../utils/utils.jsx');
var Textbox = require('./textbox.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
module.exports = React.createClass({
handleEdit: function(e) {
export default class EditPostModal extends React.Component {
constructor() {
super();
this.handleEdit = this.handleEdit.bind(this);
this.handleEditInput = this.handleEditInput.bind(this);
this.handleEditKeyPress = this.handleEditKeyPress.bind(this);
this.handleUserInput = this.handleUserInput.bind(this);
this.state = {editText: '', title: '', post_id: '', channel_id: '', comments: 0, refocusId: ''};
}
handleEdit() {
var updatedPost = {};
updatedPost.message = this.state.editText.trim();
@@ -17,8 +25,8 @@ module.exports = React.createClass({
var tempState = this.state;
delete tempState.editText;
BrowserStore.setItem('edit_state_transfer', tempState);
$("#edit_post").modal('hide');
$("#delete_post").modal('show');
$('#edit_post').modal('hide');
$('#delete_post').modal('show');
return;
}
@@ -26,79 +34,102 @@ module.exports = React.createClass({
updatedPost.channel_id = this.state.channel_id;
Client.updatePost(updatedPost,
function(data) {
function success() {
AsyncClient.getPosts(this.state.channel_id);
window.scrollTo(0, 0);
}.bind(this),
function(err) {
AsyncClient.dispatchError(err, "updatePost");
}.bind(this)
function error(err) {
AsyncClient.dispatchError(err, 'updatePost');
}
);
$("#edit_post").modal('hide');
$('#edit_post').modal('hide');
$(this.state.refocusId).focus();
},
handleEditInput: function(editMessage) {
}
handleEditInput(editMessage) {
this.setState({editText: editMessage});
},
handleEditKeyPress: function(e) {
if (e.which == 13 && !e.shiftKey && !e.altKey) {
}
handleEditKeyPress(e) {
if (e.which === 13 && !e.shiftKey && !e.altKey) {
e.preventDefault();
this.refs.editbox.getDOMNode().blur();
React.findDOMNode(this.refs.editbox).blur();
this.handleEdit(e);
}
},
handleUserInput: function(e) {
this.setState({ editText: e.target.value });
},
componentDidMount: function() {
}
handleUserInput(e) {
this.setState({editText: e.target.value});
}
componentDidMount() {
var self = this;
$(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) {
self.setState({editText: "", title: "", channel_id: "", post_id: "", comments: 0, refocusId: "", error: ''});
$(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function onHidden() {
self.setState({editText: '', title: '', channel_id: '', post_id: '', comments: 0, refocusId: '', error: ''});
});
$(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
$(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function onShow(e) {
var button = e.relatedTarget;
self.setState({ editText: $(button).attr('data-message'), title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments'), refocusId: $(button).attr('data-refoucsid') });
self.setState({editText: $(button).attr('data-message'), title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments'), refocusId: $(button).attr('data-refoucsid')});
});
$(this.refs.modal.getDOMNode()).on('shown.bs.modal', function(e) {
$(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function onShown() {
self.refs.editbox.resize();
});
},
getInitialState: function() {
return { editText: "", title: "", post_id: "", channel_id: "", comments: 0, refocusId: "" };
},
render: function() {
var error = this.state.error ? <div className='form-group has-error'><br /><label className='control-label'>{ this.state.error }</label></div> : <div className='form-group'><br /></div>;
}
render() {
var error = (<div className='form-group'><br /></div>);
if (this.state.error) {
error = (<div className='form-group has-error'><br /><label className='control-label'>{this.state.error}</label></div>);
}
return (
<div className="modal fade edit-modal" ref="modal" id="edit_post" role="dialog" tabIndex="-1" aria-hidden="true">
<div className="modal-dialog modal-push-down">
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" data-dismiss="modal" aria-label="Close" onClick={this.handleEditClose}><span aria-hidden="true">&times;</span></button>
<h4 className="modal-title">Edit {this.state.title}</h4>
</div>
<div className="edit-modal-body modal-body">
<Textbox
onUserInput={this.handleEditInput}
onKeyPress={this.handleEditKeyPress}
messageText={this.state.editText}
createMessage="Edit the post..."
id="edit_textbox"
ref="editbox"
/>
{ error }
</div>
<div className="modal-footer">
<button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" className="btn btn-primary" onClick={this.handleEdit}>Save</button>
</div>
<div
className='modal fade edit-modal'
ref='modal'
id='edit_post'
role='dialog'
tabIndex='-1'
aria-hidden='true' >
<div className='modal-dialog modal-push-down'>
<div className='modal-content'>
<div className='modal-header'>
<button
type='button'
className='close'
data-dismiss='modal'
aria-label='Close'
onClick={this.handleEditClose}>
<span aria-hidden='true'>&times;</span>
</button>
<h4 className='modal-title'>Edit {this.state.title}</h4>
</div>
<div className='edit-modal-body modal-body'>
<Textbox
onUserInput={this.handleEditInput}
onKeyPress={this.handleEditKeyPress}
messageText={this.state.editText}
createMessage='Edit the post...'
id='edit_textbox'
ref='editbox'
/>
{error}
</div>
<div className='modal-footer'>
<button
type='button'
className='btn btn-default'
data-dismiss='modal' >
Cancel
</button>
<button
type='button'
className='btn btn-primary'
onClick={this.handleEdit}>
Save
</button>
</div>
</div>
</div>
</div>
</div>
);
}
});
}

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

@@ -7,32 +7,40 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
function getStateFromStores() {
var error = ErrorStore.getLastError();
if (error && error.message !== "There appears to be a problem with your internet connection") {
return { message: error.message };
} else {
return { message: null };
export default class ErrorBar extends React.Component {
constructor() {
super();
this.onErrorChange = this.onErrorChange.bind(this);
this.handleClose = this.handleClose.bind(this);
this.state = this.getStateFromStores();
if (this.state.message) {
setTimeout(this.handleClose, 10000);
}
}
}
getStateFromStores() {
var error = ErrorStore.getLastError();
if (!error || error.message === 'There appears to be a problem with your internet connection') {
return {message: null};
}
module.exports = React.createClass({
displayName: 'ErrorBar',
componentDidMount: function() {
ErrorStore.addChangeListener(this._onChange);
return {message: error.message};
}
componentDidMount() {
ErrorStore.addChangeListener(this.onErrorChange);
$('body').css('padding-top', $(React.findDOMNode(this)).outerHeight());
$(window).resize(function() {
$(window).resize(function onResize() {
if (this.state.message) {
$('body').css('padding-top', $(React.findDOMNode(this)).outerHeight());
}
}.bind(this));
},
componentWillUnmount: function() {
ErrorStore.removeChangeListener(this._onChange);
},
_onChange: function() {
var newState = getStateFromStores();
}
componentWillUnmount() {
ErrorStore.removeChangeListener(this.onErrorChange);
}
onErrorChange() {
var newState = this.getStateFromStores();
if (!utils.areStatesEqual(newState, this.state)) {
if (newState.message) {
setTimeout(this.handleClose, 10000);
@@ -40,9 +48,11 @@ module.exports = React.createClass({
this.setState(newState);
}
},
handleClose: function(e) {
if (e) e.preventDefault();
}
handleClose(e) {
if (e) {
e.preventDefault();
}
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_ERROR,
@@ -50,24 +60,22 @@ module.exports = React.createClass({
});
$('body').css('padding-top', '0');
},
getInitialState: function() {
var state = getStateFromStores();
if (state.message) {
setTimeout(this.handleClose, 10000);
}
return state;
},
render: function() {
}
render() {
if (this.state.message) {
return (
<div className="error-bar">
<div className='error-bar'>
<span>{this.state.message}</span>
<a href="#" className="error-bar__close" onClick={this.handleClose}>&times;</a>
<a
href='#'
className='error-bar__close'
onClick={this.handleClose}>
&times;
</a>
</div>
);
} else {
return <div/>;
}
return <div/>;
}
});
}

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

@@ -5,31 +5,24 @@ var utils = require('../utils/utils.jsx');
var Client = require('../utils/client.jsx');
var Constants = require('../utils/constants.jsx');
module.exports = React.createClass({
displayName: "FileAttachment",
canSetState: false,
propTypes: {
// a list of file pathes displayed by the parent FileAttachmentList
filename: React.PropTypes.string.isRequired,
// the index of this attachment preview in the parent FileAttachmentList
index: React.PropTypes.number.isRequired,
// the identifier of the modal dialog used to preview files
modalId: React.PropTypes.string.isRequired,
// handler for when the thumbnail is clicked
handleImageClick: React.PropTypes.func
},
getInitialState: function() {
return {fileSize: -1};
},
componentDidMount: function() {
export default class FileAttachment extends React.Component {
constructor(props) {
super(props);
this.loadFiles = this.loadFiles.bind(this);
this.canSetState = false;
this.state = {fileSize: -1};
}
componentDidMount() {
this.loadFiles();
},
componentDidUpdate: function(prevProps) {
}
componentDidUpdate(prevProps) {
if (this.props.filename !== prevProps.filename) {
this.loadFiles();
}
},
loadFiles: function() {
}
loadFiles() {
this.canSetState = true;
var filename = this.props.filename;
@@ -39,91 +32,92 @@ module.exports = React.createClass({
var type = utils.getFileType(fileInfo.ext);
// This is a temporary patch to fix issue with old files using absolute paths
if (fileInfo.path.indexOf("/api/v1/files/get") != -1) {
fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1];
if (fileInfo.path.indexOf('/api/v1/files/get') !== -1) {
fileInfo.path = fileInfo.path.split('/api/v1/files/get')[1];
}
fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path;
fileInfo.path = utils.getWindowLocationOrigin() + '/api/v1/files/get' + fileInfo.path;
if (type === "image") {
var self = this;
$('<img/>').attr('src', fileInfo.path+'_thumb.jpg').load(function(path, name){ return function() {
$(this).remove();
if (name in self.refs) {
var imgDiv = self.refs[name].getDOMNode();
if (type === 'image') {
var self = this; // Need this reference since we use the given "this"
$('<img/>').attr('src', fileInfo.path + '_thumb.jpg').load(function loadWrapper(path, name) {
return function loader() {
$(this).remove();
if (name in self.refs) {
var imgDiv = React.findDOMNode(self.refs[name]);
$(imgDiv).removeClass('post__load');
$(imgDiv).addClass('post__image');
$(imgDiv).removeClass('post__load');
$(imgDiv).addClass('post__image');
var width = this.width || $(this).width();
var height = this.height || $(this).height();
var width = this.width || $(this).width();
var height = this.height || $(this).height();
if (width < Constants.THUMBNAIL_WIDTH
&& height < Constants.THUMBNAIL_HEIGHT) {
$(imgDiv).addClass('small');
} else {
$(imgDiv).addClass('normal');
if (width < Constants.THUMBNAIL_WIDTH &&
height < Constants.THUMBNAIL_HEIGHT) {
$(imgDiv).addClass('small');
} else {
$(imgDiv).addClass('normal');
}
var re1 = new RegExp(' ', 'g');
var re2 = new RegExp('\\(', 'g');
var re3 = new RegExp('\\)', 'g');
var url = path.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29');
$(imgDiv).css('background-image', 'url(' + url + '_thumb.jpg)');
}
var re1 = new RegExp(' ', 'g');
var re2 = new RegExp('\\(', 'g');
var re3 = new RegExp('\\)', 'g');
var url = path.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29');
$(imgDiv).css('background-image', 'url('+url+'_thumb.jpg)');
}
}}(fileInfo.path, filename));
}; }(fileInfo.path, filename));
}
}
},
componentWillUnmount: function() {
}
componentWillUnmount() {
// keep track of when this component is mounted so that we can asynchronously change state without worrying about whether or not we're mounted
this.canSetState = false;
},
shouldComponentUpdate: function(nextProps, nextState) {
}
shouldComponentUpdate(nextProps, nextState) {
if (!utils.areStatesEqual(nextProps, this.props)) {
return true;
}
// the only time this object should update is when it receives an updated file size which we can usually handle without re-rendering
if (nextState.fileSize != this.state.fileSize) {
if (nextState.fileSize !== this.state.fileSize) {
if (this.refs.fileSize) {
// update the UI element to display the file size without re-rendering the whole component
this.refs.fileSize.getDOMNode().innerHTML = utils.fileSizeToString(nextState.fileSize);
React.findDOMNode(this.refs.fileSize).innerHTML = utils.fileSizeToString(nextState.fileSize);
return false;
} else {
// we can't find the element that should hold the file size so we must not have rendered yet
return true;
}
} else {
// we can't find the element that should hold the file size so we must not have rendered yet
return true;
}
},
render: function() {
return true;
}
render() {
var filename = this.props.filename;
var fileInfo = utils.splitFileLocation(filename);
var type = utils.getFileType(fileInfo.ext);
var thumbnail;
if (type === "image") {
thumbnail = <div ref={filename} className="post__load" style={{backgroundImage: 'url(/static/images/load.gif)'}}/>;
if (type === 'image') {
thumbnail = (<div
ref={filename}
className='post__load'
style={{backgroundImage: 'url(/static/images/load.gif)'}} />);
} else {
thumbnail = <div className={"file-icon "+utils.getIconClassName(type)}/>;
thumbnail = <div className={'file-icon ' + utils.getIconClassName(type)}/>;
}
var fileSizeString = "";
var fileSizeString = '';
if (this.state.fileSize < 0) {
var self = this;
Client.getFileInfo(
filename,
function(data) {
if (self.canSetState) {
self.setState({fileSize: parseInt(data["size"], 10)});
function success(data) {
if (this.canSetState) {
this.setState({fileSize: parseInt(data.size, 10)});
}
},
function(err) {
}
}.bind(this),
function error() {}
);
} else {
fileSizeString = utils.fileSizeToString(this.state.fileSize);
@@ -132,25 +126,51 @@ module.exports = React.createClass({
var filenameString = decodeURIComponent(utils.getFileName(filename));
var trimmedFilename;
if (filenameString.length > 35) {
trimmedFilename = filenameString.substring(0, Math.min(35, filenameString.length)) + "...";
trimmedFilename = filenameString.substring(0, Math.min(35, filenameString.length)) + '...';
} else {
trimmedFilename = filenameString;
}
return (
<div className="post-image__column" key={filename}>
<a className="post-image__thumbnail" href="#" onClick={this.props.handleImageClick}
data-img-id={this.props.index} data-toggle="modal" data-target={"#" + this.props.modalId }>
<div
className='post-image__column'
key={filename}>
<a className='post-image__thumbnail'
href='#'
onClick={this.props.handleImageClick}
data-img-id={this.props.index}
data-toggle='modal'
data-target={'#' + this.props.modalId} >
{thumbnail}
</a>
<div className="post-image__details">
<div data-toggle="tooltip" title={filenameString} className="post-image__name">{trimmedFilename}</div>
<div className='post-image__details'>
<div
data-toggle='tooltip'
title={filenameString}
className='post-image__name' >
{trimmedFilename}
</div>
<div>
<span className="post-image__type">{fileInfo.ext.toUpperCase()}</span>
<span className="post-image__size">{fileSizeString}</span>
<span className='post-image__type'>{fileInfo.ext.toUpperCase()}</span>
<span className='post-image__size'>{fileSizeString}</span>
</div>
</div>
</div>
);
}
});
}
FileAttachment.propTypes = {
// a list of file pathes displayed by the parent FileAttachmentList
filename: React.PropTypes.string.isRequired,
// the index of this attachment preview in the parent FileAttachmentList
index: React.PropTypes.number.isRequired,
// the identifier of the modal dialog used to preview files
modalId: React.PropTypes.string.isRequired,
// handler for when the thumbnail is clicked
handleImageClick: React.PropTypes.func
};

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

@@ -5,33 +5,30 @@ var ViewImageModal = require('./view_image.jsx');
var FileAttachment = require('./file_attachment.jsx');
var Constants = require('../utils/constants.jsx');
module.exports = React.createClass({
displayName: "FileAttachmentList",
propTypes: {
// a list of file pathes displayed by this
filenames: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
// the identifier of the modal dialog used to preview files
modalId: React.PropTypes.string.isRequired,
// the channel that this is part of
channelId: React.PropTypes.string,
// the user that owns the post that this is attached to
userId: React.PropTypes.string
},
getInitialState: function() {
return {startImgId: 0};
},
render: function() {
export default class FileAttachmentList extends React.Component {
constructor(props) {
super(props);
this.state = {startImgId: 0};
}
render() {
var filenames = this.props.filenames;
var modalId = this.props.modalId;
var postFiles = [];
for (var i = 0; i < filenames.length && i < Constants.MAX_DISPLAY_FILES; i++) {
postFiles.push(<FileAttachment key={i} filename={filenames[i]} index={i} modalId={modalId} handleImageClick={this.handleImageClick} />);
postFiles.push(
<FileAttachment
key={i}
filename={filenames[i]}
index={i}
modalId={modalId}
handleImageClick={this.handleImageClick} />
);
}
return (
<div>
<div className="post-image__columns">
<div className='post-image__columns'>
{postFiles}
</div>
<ViewImageModal
@@ -42,8 +39,23 @@ module.exports = React.createClass({
filenames={filenames} />
</div>
);
},
handleImageClick: function(e) {
this.setState({startImgId: parseInt($(e.target.parentNode).attr('data-img-id'))});
}
});
handleImageClick(e) {
this.setState({startImgId: parseInt($(e.target.parentNode).attr('data-img-id'), 10)});
}
}
FileAttachmentList.propTypes = {
// a list of file pathes displayed by this
filenames: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
// the identifier of the modal dialog used to preview files
modalId: React.PropTypes.string.isRequired,
// the channel that this is part of
channelId: React.PropTypes.string,
// the user that owns the post that this is attached to
userId: React.PropTypes.string
};

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

@@ -1,64 +1,116 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var ChannelStore = require('../stores/channel_store.jsx');
var UserStore = require('../stores/user_store.jsx');
module.exports = React.createClass({
displayName: 'MemberListItem',
handleInvite: function(e) {
export default class MemberListItem extends React.Component {
constructor(props) {
super(props);
this.handleInvite = this.handleInvite.bind(this);
this.handleRemove = this.handleRemove.bind(this);
this.handleMakeAdmin = this.handleMakeAdmin.bind(this);
}
handleInvite(e) {
e.preventDefault();
this.props.handleInvite(this.props.member.id);
},
handleRemove: function(e) {
}
handleRemove(e) {
e.preventDefault();
this.props.handleRemove(this.props.member.id);
},
handleMakeAdmin: function(e) {
}
handleMakeAdmin(e) {
e.preventDefault();
this.props.handleMakeAdmin(this.props.member.id);
},
render: function() {
}
render() {
var member = this.props.member;
var isAdmin = this.props.isAdmin;
var isMemberAdmin = member.roles.indexOf("admin") > -1;
var isMemberAdmin = member.roles.indexOf('admin') > -1;
var timestamp = UserStore.getCurrentUser().update_at;
var invite;
if (member.invited && this.props.handleInvite) {
invite = <span className="member-role">Added</span>;
invite = <span className='member-role'>Added</span>;
} else if (this.props.handleInvite) {
invite = <a onClick={this.handleInvite} className="btn btn-sm btn-primary member-invite"><i className="glyphicon glyphicon-envelope"/> Add</a>;
} else if (isAdmin && !isMemberAdmin && (member.id != UserStore.getCurrentId())) {
var self = this;
invite = (
<div className="dropdown member-drop">
<a href="#" className="dropdown-toggle theme" type="button" id="channel_header_dropdown" data-toggle="dropdown" aria-expanded="true">
<span className="text-capitalize">{member.roles || 'Member'} </span>
<span className="caret"></span>
<a
onClick={this.handleInvite}
className='btn btn-sm btn-primary member-invite'>
<i className='glyphicon glyphicon-envelope'/> Add
</a>
);
} else if (isAdmin && !isMemberAdmin && (member.id !== UserStore.getCurrentId())) {
var self = this;
let makeAdminOption = null;
if (makeAdminOption) {
makeAdminOption = (
<li role='presentation'>
<a
href=''
role='menuitem'
onClick={self.handleMakeAdmin}>Make Admin
</a>
</li>);
}
let handleRemoveOption = null;
if (handleRemoveOption) {
handleRemoveOption = (
<li role='presentation'>
<a
href=''
role='menuitem'
onClick={self.handleRemove}>Remove Member
</a>
</li>);
}
invite = (
<div className='dropdown member-drop'>
<a
href='#'
className='dropdown-toggle theme'
type='button'
id='channel_header_dropdown'
data-toggle='dropdown'
aria-expanded='true' >
<span className='text-capitalize'>{member.roles || 'Member'} </span>
<span className='caret'></span>
</a>
<ul className="dropdown-menu member-menu" role="menu" aria-labelledby="channel_header_dropdown">
{ this.props.handleMakeAdmin ?
<li role="presentation"><a href="" role="menuitem" onClick={self.handleMakeAdmin}>Make Admin</a></li>
: null }
{ this.props.handleRemove ?
<li role="presentation"><a href="" role="menuitem" onClick={self.handleRemove}>Remove Member</a></li>
: null }
<ul
className='dropdown-menu member-menu'
role='menu'
aria-labelledby='channel_header_dropdown'>
{makeAdminOption}
{handleRemoveOption}
</ul>
</div>
);
} else {
invite = <div className="member-role text-capitalize">{member.roles || 'Member'}<span className="caret hidden"></span></div>;
invite = <div className='member-role text-capitalize'>{member.roles || 'Member'}<span className='caret hidden'></span></div>;
}
return (
<div className="row member-div">
<img className="post-profile-img pull-left" src={"/api/v1/users/" + member.id + "/image?time=" + timestamp} height="36" width="36" />
<span className="member-name">{member.username}</span>
<span className="member-email">{member.email}</span>
{ invite }
<div className='row member-div'>
<img
className='post-profile-img pull-left'
src={'/api/v1/users/' + member.id + '/image?time=' + timestamp}
height='36'
width='36' />
<span className='member-name'>{member.username}</span>
<span className='member-email'>{member.email}</span>
{invite}
</div>
);
}
});
}
MemberListItem.propTypes = {
handleInvite: React.PropTypes.func,
handleRemove: React.PropTypes.func,
handleMakeAdmin: React.PropTypes.func,
member: React.PropTypes.object,
isAdmin: React.PropTypes.bool
};

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

@@ -14,54 +14,66 @@ var MAX_HEIGHT_LIST = 292;
var MAX_ITEMS_IN_LIST = 25;
var ITEM_HEIGHT = 36;
module.exports = React.createClass({
displayName: 'MentionList',
componentDidMount: function() {
PostStore.addMentionDataChangeListener(this.onListenerChange);
var self = this;
export default class MentionList extends React.Component {
constructor(props) {
super(props);
$('.post-right__scroll').scroll(function(){
if($('.mentions--top').length){
$('#reply_mention_tab .mentions--top').css({ bottom: $(window).height() - $('.post-right__scroll #reply_textbox').offset().top });
this.onListenerChange = this.onListenerChange.bind(this);
this.handleClick = this.handleClick.bind(this);
this.handleMouseEnter = this.handleMouseEnter.bind(this);
this.getSelection = this.getSelection.bind(this);
this.addCurrentMention = this.addCurrentMention.bind(this);
this.addFirstMention = this.addFirstMention.bind(this);
this.isEmpty = this.isEmpty.bind(this);
this.scrollToMention = this.scrollToMention.bind(this);
this.state = {excludeUsers: [], mentionText: '-1', selectedMention: 0, selectedUsername: ''};
}
componentDidMount() {
PostStore.addMentionDataChangeListener(this.onListenerChange);
$('.post-right__scroll').scroll(function onScroll() {
if ($('.mentions--top').length) {
$('#reply_mention_tab .mentions--top').css({bottom: $(window).height() - $('.post-right__scroll #reply_textbox').offset().top});
}
});
$('body').on('keydown.mentionlist', '#' + this.props.id,
function(e) {
if (!self.isEmpty() && self.state.mentionText !== '-1' && (e.which === 13 || e.which === 9)) {
function onMentionListKey(e) {
if (!this.isEmpty() && this.state.mentionText !== '-1' && (e.which === 13 || e.which === 9)) {
e.stopPropagation();
e.preventDefault();
self.addCurrentMention();
} else if (!self.isEmpty() && self.state.mentionText !== '-1' && (e.which === 38 || e.which === 40)) {
this.addCurrentMention();
} else if (!this.isEmpty() && this.state.mentionText !== '-1' && (e.which === 38 || e.which === 40)) {
e.stopPropagation();
e.preventDefault();
if (e.which === 38) {
if (self.getSelection(self.state.selectedMention - 1)) {
self.setState({selectedMention: self.state.selectedMention - 1, selectedUsername: self.refs['mention' + (self.state.selectedMention - 1)].props.username});
if (this.getSelection(this.state.selectedMention - 1)) {
this.setState({selectedMention: this.state.selectedMention - 1, selectedUsername: this.refs['mention' + (this.state.selectedMention - 1)].props.username});
}
} else if (e.which === 40) {
if (self.getSelection(self.state.selectedMention + 1)) {
self.setState({selectedMention: self.state.selectedMention + 1, selectedUsername: self.refs['mention' + (self.state.selectedMention + 1)].props.username});
if (this.getSelection(this.state.selectedMention + 1)) {
this.setState({selectedMention: this.state.selectedMention + 1, selectedUsername: this.refs['mention' + (this.state.selectedMention + 1)].props.username});
}
}
self.scrollToMention(e.which);
this.scrollToMention(e.which);
}
}
}.bind(this)
);
$(document).click(function(e) {
if (!($('#' + self.props.id).is(e.target) || $('#' + self.props.id).has(e.target).length ||
('mentionlist' in self.refs && $(self.refs.mentionlist.getDOMNode()).has(e.target).length))) {
self.setState({mentionText: '-1'});
$(document).click(function onClick(e) {
if (!($('#' + this.props.id).is(e.target) || $('#' + this.props.id).has(e.target).length ||
('mentionlist' in this.refs && $(React.findDOMNode(this.refs.mentionlist)).has(e.target).length))) {
this.setState({mentionText: '-1'});
}
});
},
componentWillUnmount: function() {
}.bind(this));
}
componentWillUnmount() {
PostStore.removeMentionDataChangeListener(this.onListenerChange);
$('body').off('keydown.mentionlist', '#' + this.props.id);
},
componentDidUpdate: function() {
}
componentDidUpdate() {
if (this.state.mentionText !== '-1') {
if (this.state.selectedUsername !== '' && (!this.getSelection(this.state.selectedMention) || this.state.selectedUsername !== this.refs['mention' + this.state.selectedMention].props.username)) {
var tempSelectedMention = -1;
@@ -80,8 +92,8 @@ module.exports = React.createClass({
} else if (this.state.selectedMention !== 0) {
this.setState({selectedMention: 0, selectedUsername: ''});
}
},
onListenerChange: function(id, mentionText) {
}
onListenerChange(id, mentionText) {
if (id !== this.props.id) {
return;
}
@@ -92,8 +104,8 @@ module.exports = React.createClass({
}
this.setState(newState);
},
handleClick: function(name) {
}
handleClick(name) {
AppDispatcher.handleViewAction({
type: ActionTypes.RECIEVED_ADD_MENTION,
id: this.props.id,
@@ -101,33 +113,33 @@ module.exports = React.createClass({
});
this.setState({mentionText: '-1'});
},
handleMouseEnter: function(listId) {
}
handleMouseEnter(listId) {
this.setState({selectedMention: listId, selectedUsername: this.refs['mention' + listId].props.username});
},
getSelection: function(listId) {
}
getSelection(listId) {
if (!this.refs['mention' + listId]) {
return false;
}
return true;
},
addCurrentMention: function() {
}
addCurrentMention() {
if (!this.getSelection(this.state.selectedMention)) {
this.addFirstMention();
} else {
this.refs['mention' + this.state.selectedMention].handleClick();
}
},
addFirstMention: function() {
}
addFirstMention() {
if (!this.refs.mention0) {
return;
}
this.refs.mention0.handleClick();
},
isEmpty: function() {
}
isEmpty() {
return (!this.refs.mention0);
},
scrollToMention: function(keyPressed) {
}
scrollToMention(keyPressed) {
var direction;
if (keyPressed === 38) {
direction = 'up';
@@ -145,12 +157,8 @@ module.exports = React.createClass({
$('#mentionsbox').animate({
scrollTop: scrollAmount
}, 75);
},
getInitialState: function() {
return {excludeUsers: [], mentionText: '-1', selectedMention: 0, selectedUsername: ''};
},
render: function() {
var self = this;
}
render() {
var mentionText = this.state.mentionText;
if (mentionText === '-1') {
return null;
@@ -158,8 +166,10 @@ module.exports = React.createClass({
var profiles = UserStore.getActiveOnlyProfiles();
var users = [];
for (var id in profiles) {
users.push(profiles[id]);
for (let id in profiles) {
if (profiles[id]) {
users.push(profiles[id]);
}
}
var all = {};
@@ -176,7 +186,7 @@ module.exports = React.createClass({
channel.id = 'channelmention';
users.push(channel);
users.sort(function(a, b) {
users.sort(function sortByUsername(a, b) {
if (a.username < b.username) {
return -1;
}
@@ -185,29 +195,34 @@ module.exports = React.createClass({
}
return 0;
});
var mentions = {};
var mentions = [];
var index = 0;
for (var i = 0; i < users.length && index < MAX_ITEMS_IN_LIST; i++) {
if ((users[i].first_name && users[i].first_name.lastIndexOf(mentionText, 0) === 0) ||
(users[i].last_name && users[i].last_name.lastIndexOf(mentionText, 0) === 0) ||
users[i].username.lastIndexOf(mentionText, 0) === 0) {
let isFocused = '';
if (this.state.selectedMention === index) {
isFocused = 'mentions-focus';
}
mentions[index] = (
<Mention
key={'mention_key_' + index}
ref={'mention' + index}
username={users[i].username}
secondary_text={Utils.getFullName(users[i])}
id={users[i].id}
listId={index}
isFocused={this.state.selectedMention === index ? 'mentions-focus' : ''}
handleMouseEnter={function(value) { return function() { self.handleMouseEnter(value); } }(index)}
isFocused={isFocused}
handleMouseEnter={this.handleMouseEnter.bind(this, index)}
handleClick={this.handleClick} />
);
index++;
}
}
var numMentions = Object.keys(mentions).length;
var numMentions = mentions.length;
if (numMentions < 1) {
return null;
@@ -223,11 +238,20 @@ module.exports = React.createClass({
};
return (
<div className='mentions--top' style={style}>
<div ref='mentionlist' className='mentions-box' id='mentionsbox'>
<div
className='mentions--top'
style={style}>
<div
ref='mentionlist'
className='mentions-box'
id='mentionsbox'>
{mentions}
</div>
</div>
);
}
});
}
MentionList.propTypes = {
id: React.PropTypes.string
};

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

@@ -5,17 +5,24 @@ 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');
var TeamStore = require('../stores/team_store.jsx');
module.exports = React.createClass({
displayName: 'NewChannelModal',
handleSubmit: function(e) {
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 = this.refs.display_name.getDOMNode().value.trim();
channel.display_name = React.findDOMNode(this.refs.display_name).value.trim();
if (!channel.display_name) {
state.displayNameError = 'This field is required';
state.inValid = true;
@@ -26,7 +33,7 @@ module.exports = React.createClass({
state.displayNameError = '';
}
channel.name = this.refs.channel_name.getDOMNode().value.trim();
channel.name = React.findDOMNode(this.refs.channel_name).value.trim();
if (!channel.name) {
state.nameError = 'This field is required';
state.inValid = true;
@@ -52,54 +59,51 @@ module.exports = React.createClass({
var cu = UserStore.getCurrentUser();
channel.team_id = cu.team_id;
channel.description = this.refs.channel_desc.getDOMNode().value.trim();
channel.description = React.findDOMNode(this.refs.channel_desc).value.trim();
channel.type = this.state.channelType;
client.createChannel(channel,
function(data) {
$(this.refs.modal.getDOMNode()).modal('hide');
function success(data) {
$(React.findDOMNode(this.refs.modal)).modal('hide');
asyncClient.getChannel(data.id);
utils.switchChannel(data);
this.refs.display_name.getDOMNode().value = '';
this.refs.channel_name.getDOMNode().value = '';
this.refs.channel_desc.getDOMNode().value = '';
React.findDOMNode(this.refs.display_name).value = '';
React.findDOMNode(this.refs.channel_name).value = '';
React.findDOMNode(this.refs.channel_desc).value = '';
}.bind(this),
function(err) {
function error(err) {
state.serverError = err.message;
state.inValid = true;
this.setState(state);
}.bind(this)
);
},
displayNameKeyUp: function() {
var displayName = this.refs.display_name.getDOMNode().value.trim();
}
displayNameKeyUp() {
var displayName = React.findDOMNode(this.refs.display_name).value.trim();
var channelName = utils.cleanUpUrlable(displayName);
this.refs.channel_name.getDOMNode().value = channelName;
},
componentDidMount: function() {
React.findDOMNode(this.refs.channel_name).value = channelName;
}
componentDidMount() {
var self = this;
$(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
$(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function onModalShow(e) {
var button = e.relatedTarget;
self.setState({channelType: $(button).attr('data-channeltype')});
});
$(this.refs.modal.getDOMNode()).on('hidden.bs.modal', this.handleClose);
},
componentWillUnmount: function() {
$(this.refs.modal.getDOMNode()).off('hidden.bs.modal', this.handleClose);
},
handleClose: function() {
$(this.getDOMNode()).find('.form-control').each(function clearForms() {
$(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});
},
getInitialState: function() {
return {channelType: ''};
},
render: function() {
}
render() {
var displayNameError = null;
var nameError = null;
var serverError = null;
@@ -124,11 +128,20 @@ module.exports = React.createClass({
}
return (
<div className='modal fade' id='new_channel' ref='modal' tabIndex='-1' role='dialog' aria-hidden='true'>
<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'>
<button
type='button'
className='close'
data-dismiss='modal'>
<span aria-hidden='true'>&times;</span>
<span className='sr-only'>Cancel</span>
</button>
@@ -138,23 +151,49 @@ module.exports = React.createClass({
<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' />
<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' />
<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'></textarea>
<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>
<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>
@@ -162,4 +201,4 @@ module.exports = React.createClass({
</div>
);
}
});
}

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

@@ -15,9 +15,17 @@ var utils = require('../utils/utils.jsx');
var PostInfo = require('./post_info.jsx');
module.exports = React.createClass({
displayName: 'Post',
handleCommentClick: function(e) {
export default class Post extends React.Component {
constructor(props) {
super(props);
this.handleCommentClick = this.handleCommentClick.bind(this);
this.forceUpdateInfo = this.forceUpdateInfo.bind(this);
this.retryPost = this.retryPost.bind(this);
this.state = {};
}
handleCommentClick(e) {
e.preventDefault();
var data = {};
@@ -33,31 +41,31 @@ module.exports = React.createClass({
type: ActionTypes.RECIEVED_SEARCH,
results: null
});
},
forceUpdateInfo: function() {
}
forceUpdateInfo() {
this.refs.info.forceUpdate();
this.refs.header.forceUpdate();
},
retryPost: function(e) {
}
retryPost(e) {
e.preventDefault();
var post = this.props.post;
client.createPost(post, post.channel_id,
function(data) {
function success(data) {
AsyncClient.getPosts();
var channel = ChannelStore.get(post.channel_id);
var member = ChannelStore.getMember(post.channel_id);
member.msg_count = channel.total_msg_count;
member.last_viewed_at = (new Date).getTime();
member.last_viewed_at = utils.getTimestamp();
ChannelStore.setChannelMember(member);
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_POST,
post: data
});
}.bind(this),
function(err) {
},
function error() {
post.state = Constants.POST_FAILED;
PostStore.updatePendingPost(post);
this.forceUpdate();
@@ -67,18 +75,15 @@ module.exports = React.createClass({
post.state = Constants.POST_LOADING;
PostStore.updatePendingPost(post);
this.forceUpdate();
},
shouldComponentUpdate: function(nextProps) {
}
shouldComponentUpdate(nextProps) {
if (!utils.areStatesEqual(nextProps.post, this.props.post)) {
return true;
}
return false;
},
getInitialState: function() {
return { };
},
render: function() {
}
render() {
var post = this.props.post;
var parentPost = this.props.parentPost;
var posts = this.props.posts;
@@ -89,19 +94,27 @@ module.exports = React.createClass({
}
var commentCount = 0;
var commentRootId = parentPost ? post.root_id : post.id;
var commentRootId;
if (parentPost) {
commentRootId = post.root_id;
} else {
commentRootId = post.id;
}
for (var postId in posts) {
if (posts[postId].root_id == commentRootId) {
if (posts[postId].root_id === commentRootId) {
commentCount += 1;
}
}
var error = this.state.error ? <div className='form-group has-error'><label className='control-label'>{ this.state.error }</label></div> : null;
var rootUser = this.props.sameRoot ? 'same--root' : 'other--root';
var rootUser;
if (this.props.sameRoot) {
rootUser = 'same--root';
} else {
rootUser = 'other--root';
}
var postType = '';
if (type != 'Post'){
if (type !== 'Post') {
postType = 'post--comment';
}
@@ -122,21 +135,60 @@ module.exports = React.createClass({
sameUserClass = 'same--user';
}
var profilePic = null;
if (this.props.hideProfilePic) {
profilePic = (
<div className='post-profile-img__container'>
<img
className='post-profile-img'
src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp}
height='36'
width='36' />
</div>
);
}
return (
<div>
<div id={post.id} className={'post ' + sameUserClass + ' ' + rootUser + ' ' + postType + ' ' + currentUserCss}>
{ !this.props.hideProfilePic ?
<div className='post-profile-img__container'>
<img className='post-profile-img' src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp} height='36' width='36' />
</div>
: null }
<div
id={post.id}
className={'post ' + sameUserClass + ' ' + rootUser + ' ' + postType + ' ' + currentUserCss} >
{profilePic}
<div className='post__content'>
<PostHeader ref='header' post={post} sameRoot={this.props.sameRoot} commentCount={commentCount} handleCommentClick={this.handleCommentClick} isLastComment={this.props.isLastComment} />
<PostBody post={post} sameRoot={this.props.sameRoot} parentPost={parentPost} posts={posts} handleCommentClick={this.handleCommentClick} retryPost={this.retryPost} />
<PostInfo ref='info' post={post} sameRoot={this.props.sameRoot} commentCount={commentCount} handleCommentClick={this.handleCommentClick} allowReply='true' />
<PostHeader
ref='header'
post={post}
sameRoot={this.props.sameRoot}
commentCount={commentCount}
handleCommentClick={this.handleCommentClick}
isLastComment={this.props.isLastComment} />
<PostBody
post={post}
sameRoot={this.props.sameRoot}
parentPost={parentPost}
posts={posts}
handleCommentClick={this.handleCommentClick}
retryPost={this.retryPost} />
<PostInfo
ref='info'
post={post}
sameRoot={this.props.sameRoot}
commentCount={commentCount}
handleCommentClick={this.handleCommentClick}
allowReply='true' />
</div>
</div>
</div>
);
}
});
}
Post.propTypes = {
post: React.PropTypes.object,
posts: React.PropTypes.object,
parentPost: React.PropTypes.object,
sameUser: React.PropTypes.bool,
sameRoot: React.PropTypes.bool,
hideProfilePic: React.PropTypes.bool,
isLastComment: React.PropTypes.bool
};

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

@@ -1,7 +1,6 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var PostStore = require('../stores/post_store.jsx');
@@ -10,36 +9,47 @@ var utils = require('../utils/utils.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
function getSearchTermStateFromStores() {
var term = PostStore.getSearchTerm() || '';
return {
search_term: term
};
}
export default class SearchBar extends React.Component {
constructor() {
super();
this.mounted = false;
module.exports = React.createClass({
displayName: 'SearchBar',
componentDidMount: function() {
PostStore.addSearchTermChangeListener(this._onChange);
},
componentWillUnmount: function() {
PostStore.removeSearchTermChangeListener(this._onChange);
},
_onChange: function(doSearch, isMentionSearch) {
if (this.isMounted()) {
var newState = getSearchTermStateFromStores();
this.onListenerChange = this.onListenerChange.bind(this);
this.handleUserInput = this.handleUserInput.bind(this);
this.performSearch = this.performSearch.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = this.getSearchTermStateFromStores();
}
getSearchTermStateFromStores() {
var term = PostStore.getSearchTerm() || '';
return {
searchTerm: term
};
}
componentDidMount() {
PostStore.addSearchTermChangeListener(this.onListenerChange);
this.mounted = true;
}
componentWillUnmount() {
PostStore.removeSearchTermChangeListener(this.onListenerChange);
this.mounted = false;
}
onListenerChange(doSearch, isMentionSearch) {
if (this.mounted) {
var newState = this.getSearchTermStateFromStores();
if (!utils.areStatesEqual(newState, this.state)) {
this.setState(newState);
}
if (doSearch) {
this.performSearch(newState.search_term, isMentionSearch);
this.performSearch(newState.searchTerm, isMentionSearch);
}
}
},
clearFocus: function(e) {
}
clearFocus() {
$('.search-bar__container').removeClass('focused');
},
handleClose: function(e) {
}
handleClose(e) {
e.preventDefault();
AppDispatcher.handleServerAction({
@@ -58,23 +68,23 @@ module.exports = React.createClass({
type: ActionTypes.RECIEVED_POST_SELECTED,
results: null
});
},
handleUserInput: function(e) {
}
handleUserInput(e) {
var term = e.target.value;
PostStore.storeSearchTerm(term);
PostStore.emitSearchTermChange(false);
this.setState({ search_term: term });
},
handleUserFocus: function(e) {
this.setState({searchTerm: term});
}
handleUserFocus(e) {
e.target.select();
$('.search-bar__container').addClass('focused');
},
performSearch: function(terms, isMentionSearch) {
}
performSearch(terms, isMentionSearch) {
if (terms.length) {
this.setState({isSearching: true});
client.search(
terms,
function(data) {
function success(data) {
this.setState({isSearching: false});
if (utils.isMobile()) {
React.findDOMNode(this.refs.search).value = '';
@@ -86,38 +96,50 @@ module.exports = React.createClass({
is_mention_search: isMentionSearch
});
}.bind(this),
function(err) {
function error(err) {
this.setState({isSearching: false});
AsyncClient.dispatchError(err, "search");
AsyncClient.dispatchError(err, 'search');
}.bind(this)
);
}
},
handleSubmit: function(e) {
}
handleSubmit(e) {
e.preventDefault();
this.performSearch(this.state.search_term.trim());
},
getInitialState: function() {
return getSearchTermStateFromStores();
},
render: function() {
this.performSearch(this.state.searchTerm.trim());
}
render() {
var isSearching = null;
if (this.state.isSearching) {
isSearching = <span className={'glyphicon glyphicon-refresh glyphicon-refresh-animate'}></span>;
}
return (
<div>
<div className="sidebar__collapse" onClick={this.handleClose}><span className="fa fa-angle-left"></span></div>
<span onClick={this.clearFocus} className="search__clear">Cancel</span>
<form role="form" className="search__form relative-div" onSubmit={this.handleSubmit}>
<span className="glyphicon glyphicon-search sidebar__search-icon"></span>
<div
className='sidebar__collapse'
onClick={this.handleClose} >
<span className='fa fa-angle-left'></span>
</div>
<span
className='search__clear'
onClick={this.clearFocus}>
Cancel
</span>
<form
role='form'
className='search__form relative-div'
onSubmit={this.handleSubmit}>
<span className='glyphicon glyphicon-search sidebar__search-icon'></span>
<input
type="text"
ref="search"
className="form-control search-bar"
placeholder="Search"
value={this.state.search_term}
type='text'
ref='search'
className='form-control search-bar'
placeholder='Search'
value={this.state.searchTerm}
onFocus={this.handleUserFocus}
onChange={this.handleUserInput} />
{this.state.isSearching ? <span className={"glyphicon glyphicon-refresh glyphicon-refresh-animate"}></span> : null}
{isSearching}
</form>
</div>
);
}
});
}

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

@@ -1,33 +1,68 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
module.exports = React.createClass({
render: function() {
var clientError = this.props.client_error ? <div className='form-group'><label className='col-sm-12 has-error'>{ this.props.client_error }</label></div> : null;
var server_error = this.props.server_error ? <div className='form-group'><label className='col-sm-12 has-error'>{ this.props.server_error }</label></div> : null;
var extraInfo = this.props.extraInfo ? this.props.extraInfo : null;
export default class SettingItemMax extends React.Component {
render() {
var clientError = null;
if (this.props.client_error) {
clientError = (<div className='form-group'><label className='col-sm-12 has-error'>{this.props.client_error}</label></div>);
}
var serverError = null;
if (this.props.server_error) {
serverError = (<div className='form-group'><label className='col-sm-12 has-error'>{this.props.server_error}</label></div>);
}
var extraInfo = null;
if (this.props.extraInfo) {
extraInfo = this.props.extraInfo;
}
var submit = '';
if (this.props.submit) {
submit = (<a
className='btn btn-sm btn-primary'
href='#'
onClick={this.props.submit}>
Submit</a>);
}
var inputs = this.props.inputs;
return (
<ul className="section-max form-horizontal">
<li className="col-sm-12 section-title">{this.props.title}</li>
<li className="col-sm-9 col-sm-offset-3">
<ul className="setting-list">
<li className="setting-list-item">
<ul className='section-max form-horizontal'>
<li className='col-sm-12 section-title'>{this.props.title}</li>
<li className='col-sm-9 col-sm-offset-3'>
<ul className='setting-list'>
<li className='setting-list-item'>
{inputs}
{extraInfo}
</li>
<li className="setting-list-item">
<li className='setting-list-item'>
<hr />
{ server_error }
{ clientError }
{ this.props.submit ? <a className="btn btn-sm btn-primary" href="#" onClick={this.props.submit}>Submit</a> : "" }
<a className="btn btn-sm theme" href="#" onClick={this.props.updateSection}>Cancel</a>
{serverError}
{clientError}
{submit}
<a
className='btn btn-sm theme'
href='#'
onClick={this.props.updateSection} >
Cancel
</a>
</li>
</ul>
</li>
</ul>
);
}
});
}
SettingItemMax.propTypes = {
inputs: React.PropTypes.array,
client_error: React.PropTypes.string,
server_error: React.PropTypes.string,
extraInfo: React.PropTypes.element,
updateSection: React.PropTypes.func,
submit: React.PropTypes.func,
title: React.PropTypes.string
};

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

@@ -4,21 +4,24 @@
var utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx');
module.exports = React.createClass({
displayName: 'TeamSignupDisplayNamePage',
propTypes: {
state: React.PropTypes.object,
updateParent: React.PropTypes.func
},
submitBack: function(e) {
export default class TeamSignupDisplayNamePage extends React.Component {
constructor(props) {
super(props);
this.submitBack = this.submitBack.bind(this);
this.submitNext = this.submitNext.bind(this);
this.state = {};
}
submitBack(e) {
e.preventDefault();
this.props.state.wizard = 'welcome';
this.props.updateParent(this.props.state);
},
submitNext: function(e) {
}
submitNext(e) {
e.preventDefault();
var displayName = this.refs.name.getDOMNode().value.trim();
var displayName = React.findDOMNode(this.refs.name).value.trim();
if (!displayName) {
this.setState({nameError: 'This field is required'});
return;
@@ -28,15 +31,12 @@ module.exports = React.createClass({
this.props.state.team.display_name = displayName;
this.props.state.team.name = utils.cleanUpUrlable(displayName);
this.props.updateParent(this.props.state);
},
getInitialState: function() {
return {};
},
handleFocus: function(e) {
}
handleFocus(e) {
e.preventDefault();
e.currentTarget.select();
},
render: function() {
}
render() {
client.track('signup', 'signup_team_02_name');
var nameError = null;
@@ -49,24 +49,48 @@ module.exports = React.createClass({
return (
<div>
<form>
<img className='signup-team-logo' src='/static/images/logo.png' />
<img
className='signup-team-logo'
src='/static/images/logo.png' />
<h2>{utils.toTitleCase(strings.Team) + ' Name'}</h2>
<div className={nameDivClass}>
<div className='row'>
<div className='col-sm-9'>
<input type='text' ref='name' className='form-control' placeholder='' maxLength='128' defaultValue={this.props.state.team.display_name} autoFocus={true} onFocus={this.handleFocus} />
<input
type='text'
ref='name'
className='form-control'
placeholder=''
maxLength='128'
defaultValue={this.props.state.team.display_name}
autoFocus={true}
onFocus={this.handleFocus} />
</div>
</div>
{nameError}
</div>
<div>{'Name your ' + strings.Team + ' in any language. Your ' + strings.Team + ' name shows in menus and headings.'}</div>
<button type='submit' className='btn btn-primary margin--extra' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button>
<button
type='submit'
className='btn btn-primary margin--extra'
onClick={this.submitNext} >
Next<i className='glyphicon glyphicon-chevron-right'></i>
</button>
<div className='margin--extra'>
<a href='#' onClick={this.submitBack}>Back to previous step</a>
<a
href='#'
onClick={this.submitBack}>
Back to previous step
</a>
</div>
</form>
</div>
);
}
});
}
TeamSignupDisplayNamePage.propTypes = {
state: React.PropTypes.object,
updateParent: React.PropTypes.func
};

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

@@ -194,7 +194,7 @@ export default class UserSettingsGeneralTab extends React.Component {
this.props.updateSection(section);
}
handleClose() {
$(this.getDOMNode()).find('.form-control').each(function clearForms() {
$(React.findDOMNode(this)).find('.form-control').each(function clearForms() {
this.value = '';
});
@@ -230,7 +230,6 @@ export default class UserSettingsGeneralTab extends React.Component {
}
var nameSection;
var self = this;
var inputs = [];
if (this.props.activeSection === 'name') {
@@ -276,9 +275,9 @@ export default class UserSettingsGeneralTab extends React.Component {
server_error={serverError}
client_error={clientError}
updateSection={function clearSection(e) {
self.updateSection('');
this.updateSection('');
e.preventDefault();
}}
}.bind(this)}
/>
);
} else {
@@ -297,8 +296,8 @@ export default class UserSettingsGeneralTab extends React.Component {
title='Full Name'
describe={fullName}
updateSection={function updateNameSection() {
self.updateSection('name');
}}
this.updateSection('name');
}.bind(this)}
/>
);
}
@@ -335,9 +334,9 @@ export default class UserSettingsGeneralTab extends React.Component {
server_error={serverError}
client_error={clientError}
updateSection={function clearSection(e) {
self.updateSection('');
this.updateSection('');
e.preventDefault();
}}
}.bind(this)}
/>
);
} else {
@@ -346,8 +345,8 @@ export default class UserSettingsGeneralTab extends React.Component {
title='Nickname'
describe={UserStore.getCurrentUser().nickname}
updateSection={function updateNicknameSection() {
self.updateSection('nickname');
}}
this.updateSection('nickname');
}.bind(this)}
/>
);
}
@@ -384,9 +383,9 @@ export default class UserSettingsGeneralTab extends React.Component {
server_error={serverError}
client_error={clientError}
updateSection={function clearSection(e) {
self.updateSection('');
this.updateSection('');
e.preventDefault();
}}
}.bind(this)}
/>
);
} else {
@@ -395,8 +394,8 @@ export default class UserSettingsGeneralTab extends React.Component {
title='Username'
describe={UserStore.getCurrentUser().username}
updateSection={function updateUsernameSection() {
self.updateSection('username');
}}
this.updateSection('username');
}.bind(this)}
/>
);
}
@@ -433,9 +432,9 @@ export default class UserSettingsGeneralTab extends React.Component {
server_error={serverError}
client_error={emailError}
updateSection={function clearSection(e) {
self.updateSection('');
this.updateSection('');
e.preventDefault();
}}
}.bind(this)}
/>
);
} else {
@@ -444,8 +443,8 @@ export default class UserSettingsGeneralTab extends React.Component {
title='Email'
describe={UserStore.getCurrentUser().email}
updateSection={function updateEmailSection() {
self.updateSection('email');
}}
this.updateSection('email');
}.bind(this)}
/>
);
}
@@ -460,9 +459,9 @@ export default class UserSettingsGeneralTab extends React.Component {
server_error={serverError}
client_error={clientError}
updateSection={function clearSection(e) {
self.updateSection('');
this.updateSection('');
e.preventDefault();
}}
}.bind(this)}
picture={this.state.picture}
pictureChange={this.updatePicture}
submitActive={this.submitActive}
@@ -479,8 +478,8 @@ export default class UserSettingsGeneralTab extends React.Component {
title='Profile Picture'
describe={minMessage}
updateSection={function updatePictureSection() {
self.updateSection('picture');
}}
this.updateSection('picture');
}.bind(this)}
/>
);
}