Merge pull request #538 from hmhealey/mm2064

MM-2064 Cosmetic refactoring for ES6 and style guide
Этот коммит содержится в:
Corey Hulen
2015-09-02 12:41:01 -07:00
родитель 3e431b6c88 7d07bf6a79
Коммит eaeab0d645
24 изменённых файлов: 2077 добавлений и 1224 удалений

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

@@ -1,102 +1,104 @@
// 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 UserStore = require('../stores/user_store.jsx'); const UserStore = require('../stores/user_store.jsx');
var Client = require('../utils/client.jsx'); const Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx'); const AsyncClient = require('../utils/async_client.jsx');
var LoadingScreen = require('./loading_screen.jsx'); const LoadingScreen = require('./loading_screen.jsx');
var utils = require('../utils/utils.jsx'); const Utils = require('../utils/utils.jsx');
function getStateFromStoresForSessions() { export default class ActivityLogModal extends React.Component {
return { constructor(props) {
sessions: UserStore.getSessions(), super(props);
serverError: null,
clientError: null
};
}
module.exports = React.createClass({ this.submitRevoke = this.submitRevoke.bind(this);
displayName: 'ActivityLogModal', this.onListenerChange = this.onListenerChange.bind(this);
submitRevoke: function(altId) { this.handleMoreInfo = this.handleMoreInfo.bind(this);
this.state = this.getStateFromStores();
this.state.moreInfo = [];
}
getStateFromStores() {
return {
sessions: UserStore.getSessions(),
serverError: null,
clientError: null
};
}
submitRevoke(altId) {
Client.revokeSession(altId, Client.revokeSession(altId,
function(data) { function handleRevokeSuccess() {
AsyncClient.getSessions(); AsyncClient.getSessions();
}.bind(this), },
function(err) { function handleRevokeError(err) {
var state = getStateFromStoresForSessions(); let state = this.getStateFromStores();
state.serverError = err; state.serverError = err;
this.setState(state); this.setState(state);
}.bind(this) }.bind(this)
); );
}, }
componentDidMount: function() { componentDidMount() {
UserStore.addSessionsChangeListener(this.onListenerChange); UserStore.addSessionsChangeListener(this.onListenerChange);
$(this.refs.modal.getDOMNode()).on('shown.bs.modal', function (e) { $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function handleShow() {
AsyncClient.getSessions(); AsyncClient.getSessions();
}); });
var self = this; $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function handleHide() {
$(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) {
$('#user_settings').modal('show'); $('#user_settings').modal('show');
self.setState({moreInfo: []}); this.setState({moreInfo: []});
}); }.bind(this));
}, }
componentWillUnmount: function() { componentWillUnmount() {
UserStore.removeSessionsChangeListener(this.onListenerChange); UserStore.removeSessionsChangeListener(this.onListenerChange);
}, }
onListenerChange: function() { onListenerChange() {
var newState = getStateFromStoresForSessions(); const newState = this.getStateFromStores();
if (!utils.areStatesEqual(newState.sessions, this.state.sessions)) { if (!Utils.areStatesEqual(newState.sessions, this.state.sessions)) {
this.setState(newState); this.setState(newState);
} }
}, }
handleMoreInfo: function(index) { handleMoreInfo(index) {
var newMoreInfo = this.state.moreInfo; let newMoreInfo = this.state.moreInfo;
newMoreInfo[index] = true; newMoreInfo[index] = true;
this.setState({moreInfo: newMoreInfo}); this.setState({moreInfo: newMoreInfo});
}, }
getInitialState: function() { render() {
var initialState = getStateFromStoresForSessions(); let activityList = [];
initialState.moreInfo = [];
return initialState;
},
render: function() {
var activityList = [];
var serverError = this.state.serverError;
// Squash any false-y value for server error into null for (let i = 0; i < this.state.sessions.length; i++) {
if (!serverError) { const currentSession = this.state.sessions[i];
serverError = null; const lastAccessTime = new Date(currentSession.last_activity_at);
} const firstAccessTime = new Date(currentSession.create_at);
let devicePicture = '';
for (var i = 0; i < this.state.sessions.length; i++) {
var currentSession = this.state.sessions[i];
var lastAccessTime = new Date(currentSession.last_activity_at);
var firstAccessTime = new Date(currentSession.create_at);
var devicePicture = '';
if (currentSession.props.platform === 'Windows') { if (currentSession.props.platform === 'Windows') {
devicePicture = 'fa fa-windows'; devicePicture = 'fa fa-windows';
} } else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') {
else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') {
devicePicture = 'fa fa-apple'; devicePicture = 'fa fa-apple';
} } else if (currentSession.props.platform === 'Linux') {
else if (currentSession.props.platform === 'Linux') {
devicePicture = 'fa fa-linux'; devicePicture = 'fa fa-linux';
} }
var moreInfo; let moreInfo;
if (this.state.moreInfo[i]) { if (this.state.moreInfo[i]) {
moreInfo = ( moreInfo = (
<div> <div>
<div>{'First time active: ' + firstAccessTime.toDateString() + ', ' + lastAccessTime.toLocaleTimeString()}</div> <div>{`First time active: ${firstAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}</div>
<div>{'OS: ' + currentSession.props.os}</div> <div>{`OS: ${currentSession.props.os}`}</div>
<div>{'Browser: ' + currentSession.props.browser}</div> <div>{`Browser: ${currentSession.props.browser}`}</div>
<div>{'Session ID: ' + currentSession.alt_id}</div> <div>{`Session ID: ${currentSession.alt_id}`}</div>
</div> </div>
); );
} else { } else {
moreInfo = (<a className='theme' href='#' onClick={this.handleMoreInfo.bind(this, i)}>More info</a>); moreInfo = (
<a
className='theme'
href='#'
onClick={this.handleMoreInfo.bind(this, i)}
>
More info
</a>
);
} }
activityList[i] = ( activityList[i] = (
@@ -104,33 +106,62 @@ module.exports = React.createClass({
<div className='activity-log__report'> <div className='activity-log__report'>
<div className='report__platform'><i className={devicePicture} />{currentSession.props.platform}</div> <div className='report__platform'><i className={devicePicture} />{currentSession.props.platform}</div>
<div className='report__info'> <div className='report__info'>
<div>{'Last activity: ' + lastAccessTime.toDateString() + ', ' + lastAccessTime.toLocaleTimeString()}</div> <div>{`Last activity: ${lastAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}</div>
{moreInfo} {moreInfo}
</div> </div>
</div> </div>
<div className='activity-log__action'><button onClick={this.submitRevoke.bind(this, currentSession.alt_id)} className='btn btn-primary'>Logout</button></div> <div className='activity-log__action'>
<button
onClick={this.submitRevoke.bind(this, currentSession.alt_id)}
className='btn btn-primary'
>
Logout
</button>
</div>
</div> </div>
); );
} }
var content; let content;
if (this.state.sessions.loading) { if (this.state.sessions.loading) {
content = (<LoadingScreen />); content = <LoadingScreen />;
} else { } else {
content = (<form role='form'>{activityList}</form>); content = <form role='form'>{activityList}</form>;
} }
return ( return (
<div> <div>
<div className='modal fade' ref='modal' id='activity-log' tabIndex='-1' role='dialog' aria-hidden='true'> <div
className='modal fade'
ref='modal'
id='activity-log'
tabIndex='-1'
role='dialog'
aria-hidden='true'
>
<div className='modal-dialog modal-lg'> <div className='modal-dialog modal-lg'>
<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
<h4 className='modal-title' id='myModalLabel'>Active Sessions</h4> type='button'
className='close'
data-dismiss='modal'
aria-label='Close'
>
<span aria-hidden='true'>&times;</span>
</button>
<h4
className='modal-title'
id='myModalLabel'
>
Active Sessions
</h4>
</div> </div>
<p className='session-help-text'>Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the 'Logout' button below to end a session.</p> <p className='session-help-text'>Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the 'Logout' button below to end a session.</p>
<div ref='modalBody' className='modal-body'> <div
ref='modalBody'
className='modal-body'
>
{content} {content}
</div> </div>
</div> </div>
@@ -139,4 +170,4 @@ module.exports = React.createClass({
</div> </div>
); );
} }
}); }

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

@@ -1,139 +1,85 @@
// 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 ChannelStore = require('../stores/channel_store.jsx'); const ChannelStore = require('../stores/channel_store.jsx');
var UserStore = require('../stores/user_store.jsx'); const UserStore = require('../stores/user_store.jsx');
var PostStore = require('../stores/post_store.jsx'); const PostStore = require('../stores/post_store.jsx');
var SocketStore = require('../stores/socket_store.jsx'); const SocketStore = require('../stores/socket_store.jsx');
var NavbarSearchBox = require('./search_bar.jsx'); const NavbarSearchBox = require('./search_bar.jsx');
var AsyncClient = require('../utils/async_client.jsx'); const AsyncClient = require('../utils/async_client.jsx');
var Client = require('../utils/client.jsx'); const Client = require('../utils/client.jsx');
var utils = require('../utils/utils.jsx'); const Utils = require('../utils/utils.jsx');
var MessageWrapper = require('./message_wrapper.jsx'); const MessageWrapper = require('./message_wrapper.jsx');
const PopoverListMembers = require('./popover_list_members.jsx');
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Constants = require('../utils/constants.jsx'); const Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes; const ActionTypes = Constants.ActionTypes;
var PopoverListMembers = React.createClass({ export default class ChannelHeader extends React.Component {
componentDidMount: function() { constructor(props) {
var originalLeave = $.fn.popover.Constructor.prototype.leave; super(props);
$.fn.popover.Constructor.prototype.leave = function(obj) {
var selfObj;
if (obj instanceof this.constructor) {
selfObj = obj;
} else {
selfObj = $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type);
}
originalLeave.call(this, obj);
if (obj.currentTarget && selfObj.$tip) { this.onListenerChange = this.onListenerChange.bind(this);
selfObj.$tip.one('mouseenter', function() { this.onSocketChange = this.onSocketChange.bind(this);
clearTimeout(selfObj.timeout); this.handleLeave = this.handleLeave.bind(this);
selfObj.$tip.one('mouseleave', function() { this.searchMentions = this.searchMentions.bind(this);
$.fn.popover.Constructor.prototype.leave.call(selfObj, selfObj);
});
});
}
};
$('#member_popover').popover({placement: 'bottom', trigger: 'click', html: true}); this.state = this.getStateFromStores();
$('body').on('click', function(e) {
if ($(e.target.parentNode.parentNode)[0] !== $('#member_popover')[0] && $(e.target).parents('.popover.in').length === 0) {
$('#member_popover').popover('hide');
}
});
},
render: function() {
var popoverHtml = '';
var members = this.props.members;
var count;
if (members.length > 20) {
count = '20+';
} else {
count = members.length || '-';
}
if (members) {
members.sort(function(a, b) {
return a.username.localeCompare(b.username);
});
members.forEach(function(m) {
popoverHtml += "<div class='text--nowrap'>" + m.username + '</div>';
});
}
return (
<div id='member_popover' data-toggle='popover' data-content={popoverHtml} data-original-title='Members' >
<div id='member_tooltip' data-placement='left' data-toggle='tooltip' title='View Channel Members'>
{count} <span className='glyphicon glyphicon-user' aria-hidden='true'></span>
</div>
</div>
);
} }
}); getStateFromStores() {
return {
function getStateFromStores() { channel: ChannelStore.getCurrent(),
return { memberChannel: ChannelStore.getCurrentMember(),
channel: ChannelStore.getCurrent(), memberTeam: UserStore.getCurrentUser(),
memberChannel: ChannelStore.getCurrentMember(), users: ChannelStore.getCurrentExtraInfo().members,
memberTeam: UserStore.getCurrentUser(), searchVisible: PostStore.getSearchResults() !== null
users: ChannelStore.getCurrentExtraInfo().members, };
searchVisible: PostStore.getSearchResults() != null }
}; componentDidMount() {
}
module.exports = React.createClass({
displayName: 'ChannelHeader',
componentDidMount: function() {
ChannelStore.addChangeListener(this.onListenerChange); ChannelStore.addChangeListener(this.onListenerChange);
ChannelStore.addExtraInfoChangeListener(this.onListenerChange); ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
PostStore.addSearchChangeListener(this.onListenerChange); PostStore.addSearchChangeListener(this.onListenerChange);
UserStore.addChangeListener(this.onListenerChange); UserStore.addChangeListener(this.onListenerChange);
SocketStore.addChangeListener(this.onSocketChange); SocketStore.addChangeListener(this.onSocketChange);
}, }
componentWillUnmount: function() { componentWillUnmount() {
ChannelStore.removeChangeListener(this.onListenerChange); ChannelStore.removeChangeListener(this.onListenerChange);
ChannelStore.removeExtraInfoChangeListener(this.onListenerChange); ChannelStore.removeExtraInfoChangeListener(this.onListenerChange);
PostStore.removeSearchChangeListener(this.onListenerChange); PostStore.removeSearchChangeListener(this.onListenerChange);
UserStore.addChangeListener(this.onListenerChange); UserStore.addChangeListener(this.onListenerChange);
}, }
onListenerChange: function() { onListenerChange() {
var newState = getStateFromStores(); const newState = this.getStateFromStores();
if (!utils.areStatesEqual(newState, this.state)) { if (!Utils.areStatesEqual(newState, this.state)) {
this.setState(newState); this.setState(newState);
} }
$('.channel-header__info .description').popover({placement: 'bottom', trigger: 'hover click', html: true, delay: {show: 500, hide: 500}}); $('.channel-header__info .description').popover({placement: 'bottom', trigger: 'hover click', html: true, delay: {show: 500, hide: 500}});
}, }
onSocketChange: function(msg) { onSocketChange(msg) {
if (msg.action === 'new_user') { if (msg.action === 'new_user') {
AsyncClient.getChannelExtraInfo(true); AsyncClient.getChannelExtraInfo(true);
} }
}, }
getInitialState: function() { handleLeave() {
return getStateFromStores();
},
handleLeave: function() {
Client.leaveChannel(this.state.channel.id, Client.leaveChannel(this.state.channel.id,
function() { function handleLeaveSuccess() {
var townsquare = ChannelStore.getByName('town-square'); const townsquare = ChannelStore.getByName('town-square');
utils.switchChannel(townsquare); Utils.switchChannel(townsquare);
}, },
function(err) { function handleLeaveError(err) {
AsyncClient.dispatchError(err, 'handleLeave'); AsyncClient.dispatchError(err, 'handleLeave');
} }
); );
}, }
searchMentions: function(e) { searchMentions(e) {
e.preventDefault(); e.preventDefault();
var user = UserStore.getCurrentUser(); const user = UserStore.getCurrentUser();
var terms = ''; let terms = '';
if (user.notify_props && user.notify_props.mention_keys) { if (user.notify_props && user.notify_props.mention_keys) {
var termKeys = UserStore.getCurrentMentionKeys(); let termKeys = UserStore.getCurrentMentionKeys();
if (user.notify_props.all === 'true' && termKeys.indexOf('@all') !== -1) { if (user.notify_props.all === 'true' && termKeys.indexOf('@all') !== -1) {
termKeys.splice(termKeys.indexOf('@all'), 1); termKeys.splice(termKeys.indexOf('@all'), 1);
} }
@@ -149,23 +95,23 @@ module.exports = React.createClass({
do_search: true, do_search: true,
is_mention_search: true is_mention_search: true
}); });
}, }
render: function() { render() {
if (this.state.channel == null) { if (this.state.channel === null) {
return null; return null;
} }
var channel = this.state.channel; const channel = this.state.channel;
var description = utils.textToJsx(channel.description, {singleline: true, noMentionHighlight: true}); const description = Utils.textToJsx(channel.description, {singleline: true, noMentionHighlight: true});
var popoverContent = React.renderToString(<MessageWrapper message={channel.description}/>); const popoverContent = React.renderToString(<MessageWrapper message={channel.description}/>);
var channelTitle = channel.display_name; let channelTitle = channel.display_name;
var currentId = UserStore.getCurrentId(); const currentId = UserStore.getCurrentId();
var isAdmin = this.state.memberChannel.roles.indexOf('admin') > -1 || this.state.memberTeam.roles.indexOf('admin') > -1; const isAdmin = this.state.memberChannel.roles.indexOf('admin') > -1 || this.state.memberTeam.roles.indexOf('admin') > -1;
var isDirect = (this.state.channel.type === 'D'); const isDirect = (this.state.channel.type === 'D');
if (isDirect) { if (isDirect) {
if (this.state.users.length > 1) { if (this.state.users.length > 1) {
var contact; let contact;
if (this.state.users[0].id === currentId) { if (this.state.users[0].id === currentId) {
contact = this.state.users[1]; contact = this.state.users[1];
} else { } else {
@@ -175,64 +121,244 @@ module.exports = React.createClass({
} }
} }
var channelTerm = 'Channel'; let channelTerm = 'Channel';
if (channel.type === 'P') { if (channel.type === 'P') {
channelTerm = 'Group'; channelTerm = 'Group';
} }
let dropdownContents = [];
if (!isDirect) {
dropdownContents.push(
<li
key='view_info'
role='presentation'
>
<a
role='menuitem'
data-toggle='modal'
data-target='#channel_info'
data-channelid={channel.id}
href='#'
>
View Info
</a>
</li>
);
if (!ChannelStore.isDefault(channel)) {
dropdownContents.push(
<li
key='add_members'
role='presentation'
>
<a
role='menuitem'
data-toggle='modal'
data-target='#channel_invite'
href='#'
>
Add Members
</a>
</li>
);
if (isAdmin) {
dropdownContents.push(
<li
key='manage_members'
role='presentation'
>
<a
role='menuitem'
data-toggle='modal'
data-target='#channel_members'
href='#'
>
Manage Members
</a>
</li>
);
}
}
dropdownContents.push(
<li
key='set_channel_description'
role='presentation'
>
<a
role='menuitem'
href='#'
data-toggle='modal'
data-target='#edit_channel'
data-desc={channel.description}
data-title={channel.display_name}
data-channelid={channel.id}
>
Set {channelTerm} Description...
</a>
</li>
);
dropdownContents.push(
<li
key='notification_preferences'
role='presentation'
>
<a
role='menuitem'
href='#'
data-toggle='modal'
data-target='#channel_notifications'
data-title={channel.display_name}
data-channelid={channel.id}
>
Notification Preferences
</a>
</li>
);
if (!ChannelStore.isDefault(channel)) {
if (isAdmin) {
dropdownContents.push(
<li
key='rename_channel'
role='presentation'
>
<a
role='menuitem'
href='#'
data-toggle='modal'
data-target='#rename_channel'
data-display={channel.display_name}
data-name={channel.name}
data-channelid={channel.id}
>
Rename {channelTerm}...
</a>
</li>
);
dropdownContents.push(
<li
key='delete_channel'
role='presentation'
>
<a
role='menuitem'
href='#'
data-toggle='modal'
data-target='#delete_channel'
data-title={channel.display_name}
data-channelid={channel.id}
>
Delete {channelTerm}...
</a>
</li>
);
}
dropdownContents.push(
<li
key='leave_channel'
role='presentation'
>
<a
role='menuitem'
href='#'
onClick={this.handleLeave}
>
Leave {channelTerm}
</a>
</li>
);
}
} else {
dropdownContents.push(
<li
key='edit_description_direct'
role='presentation'
>
<a
role='menuitem'
href='#'
data-toggle='modal'
data-target='#edit_channel'
data-desc={channel.description}
data-title={channel.display_name}
data-channelid={channel.id}
>
Set Channel Description...
</a>
</li>
);
}
return ( return (
<table className='channel-header alt'> <table className='channel-header alt'>
<tr> <tr>
<th> <th>
<div className='channel-header__info'> <div className='channel-header__info'>
<div className='dropdown'> <div className='dropdown'>
<a href='#' className='dropdown-toggle theme' type='button' id='channel_header_dropdown' data-toggle='dropdown' aria-expanded='true'> <a
href='#'
className='dropdown-toggle theme'
type='button'
id='channel_header_dropdown'
data-toggle='dropdown'
aria-expanded='true'
>
<strong className='heading'>{channelTitle} </strong> <strong className='heading'>{channelTitle} </strong>
<span className='glyphicon glyphicon-chevron-down header-dropdown__icon'></span> <span className='glyphicon glyphicon-chevron-down header-dropdown__icon' />
</a> </a>
{!isDirect ? <ul
<ul className='dropdown-menu' role='menu' aria-labelledby='channel_header_dropdown'> className='dropdown-menu'
<li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_info' data-channelid={channel.id} href='#'>View Info</a></li> role='menu'
{!ChannelStore.isDefault(channel) ? aria-labelledby='channel_header_dropdown'
<li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_invite' href='#'>Add Members</a></li> >
: null {dropdownContents}
}
{isAdmin && !ChannelStore.isDefault(channel) ?
<li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_members' href='#'>Manage Members</a></li>
: null
}
<li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#edit_channel' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set {channelTerm} Description...</a></li>
<li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#channel_notifications' data-title={channel.display_name} data-channelid={channel.id}>Notification Preferences</a></li>
{isAdmin && !ChannelStore.isDefault(channel) ?
<li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#rename_channel' data-display={channel.display_name} data-name={channel.name} data-channelid={channel.id}>Rename {channelTerm}...</a></li>
: null
}
{isAdmin && !ChannelStore.isDefault(channel) ?
<li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#delete_channel' data-title={channel.display_name} data-channelid={channel.id}>Delete {channelTerm}...</a></li>
: null
}
{!ChannelStore.isDefault(channel) ?
<li role='presentation'><a role='menuitem' href='#' onClick={this.handleLeave}>Leave {channelTerm}</a></li>
: null
}
</ul> </ul>
:
<ul className='dropdown-menu' role='menu' aria-labelledby='channel_header_dropdown'>
<li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#edit_channel' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set Channel Description...</a></li>
</ul>
}
</div> </div>
<div data-toggle='popover' data-content={popoverContent} className='description'>{description}</div> <div
data-toggle='popover'
data-content={popoverContent}
className='description'
>
{description}
</div>
</div> </div>
</th> </th>
<th><PopoverListMembers members={this.state.users} channelId={channel.id} /></th> <th>
<PopoverListMembers
members={this.state.users}
channelId={channel.id}
/>
</th>
<th className='search-bar__container'><NavbarSearchBox /></th> <th className='search-bar__container'><NavbarSearchBox /></th>
<th> <th>
<div className='dropdown channel-header__links'> <div className='dropdown channel-header__links'>
<a href='#' className='dropdown-toggle theme' type='button' id='channel_header_right_dropdown' data-toggle='dropdown' aria-expanded='true'> <a
<span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} /> </a> href='#'
<ul className='dropdown-menu dropdown-menu-right' role='menu' aria-labelledby='channel_header_right_dropdown'> className='dropdown-toggle theme'
<li role='presentation'><a role='menuitem' href='#' onClick={this.searchMentions}>Recent Mentions</a></li> type='button'
id='channel_header_right_dropdown'
data-toggle='dropdown'
aria-expanded='true'
>
<span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} />
</a>
<ul
className='dropdown-menu dropdown-menu-right'
role='menu'
aria-labelledby='channel_header_right_dropdown'
>
<li role='presentation'>
<a
role='menuitem'
href='#'
onClick={this.searchMentions}
>
Recent Mentions
</a>
</li>
</ul> </ul>
</div> </div>
</th> </th>
@@ -240,4 +366,4 @@ module.exports = React.createClass({
</table> </table>
); );
} }
}); }

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

@@ -1,154 +1,200 @@
// 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 UserStore = require('../stores/user_store.jsx'); const UserStore = require('../stores/user_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx'); const ChannelStore = require('../stores/channel_store.jsx');
var AsyncClient = require('../utils/async_client.jsx'); const AsyncClient = require('../utils/async_client.jsx');
var MemberList = require('./member_list.jsx'); const MemberList = require('./member_list.jsx');
var client = require('../utils/client.jsx'); const Client = require('../utils/client.jsx');
var utils = require('../utils/utils.jsx'); const Utils = require('../utils/utils.jsx');
function getStateFromStores() { export default class ChannelMembers extends React.Component {
var users = UserStore.getActiveOnlyProfiles(); constructor(props) {
var member_list = ChannelStore.getCurrentExtraInfo().members; super(props);
var nonmember_list = []; this.getStateFromStores = this.getStateFromStores.bind(this);
for (var id in users) { this.onChange = this.onChange.bind(this);
var found = false; this.handleRemove = this.handleRemove.bind(this);
for (var i = 0; i < member_list.length; i++) {
if (member_list[i].id === id) { this.state = this.getStateFromStores();
found = true; }
break; getStateFromStores() {
const users = UserStore.getActiveOnlyProfiles();
let memberList = ChannelStore.getCurrentExtraInfo().members;
let nonmemberList = [];
for (let id in users) {
if (users.hasOwnProperty(id)) {
let found = false;
for (let i = 0; i < memberList.length; i++) {
if (memberList[i].id === id) {
found = true;
break;
}
}
if (!found) {
nonmemberList.push(users[id]);
}
} }
} }
if (!found) {
nonmember_list.push(users[id]); function compareByUsername(a, b) {
if (a.username < b.username) {
return -1;
} else if (a.username > b.username) {
return 1;
}
return 0;
}
memberList.sort(compareByUsername);
nonmemberList.sort(compareByUsername);
const channel = ChannelStore.getCurrent();
let channelName = '';
if (channel) {
channelName = channel.display_name;
}
return {
nonmemberList: nonmemberList,
memberList: memberList,
channelName: channelName
};
}
componentDidMount() {
ChannelStore.addExtraInfoChangeListener(this.onChange);
ChannelStore.addChangeListener(this.onChange);
$(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function handleHide() {
this.setState({renderMembers: false});
}.bind(this));
$(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow() {
this.setState({renderMembers: true});
}.bind(this));
}
componentWillUnmount() {
ChannelStore.removeExtraInfoChangeListener(this.onChange);
ChannelStore.removeChangeListener(this.onChange);
}
onChange() {
const newState = this.getStateFromStores();
if (!Utils.areStatesEqual(this.state, newState)) {
this.setState(newState);
} }
} }
handleRemove(userId) {
member_list.sort(function(a,b) {
if (a.username < b.username) return -1;
if (a.username > b.username) return 1;
return 0;
});
nonmember_list.sort(function(a,b) {
if (a.username < b.username) return -1;
if (a.username > b.username) return 1;
return 0;
});
var channel_name = ChannelStore.getCurrent() ? ChannelStore.getCurrent().display_name : "";
return {
nonmember_list: nonmember_list,
member_list: member_list,
channel_name: channel_name
};
}
module.exports = React.createClass({
componentDidMount: function() {
ChannelStore.addExtraInfoChangeListener(this._onChange);
ChannelStore.addChangeListener(this._onChange);
var self = this;
$(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) {
self.setState({ render_members: false });
});
$(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
self.setState({ render_members: true });
});
},
componentWillUnmount: function() {
ChannelStore.removeExtraInfoChangeListener(this._onChange);
ChannelStore.removeChangeListener(this._onChange);
},
_onChange: function() {
var new_state = getStateFromStores();
if (!utils.areStatesEqual(this.state, new_state)) {
this.setState(new_state);
}
},
handleRemove: function(user_id) {
// Make sure the user is a member of the channel // Make sure the user is a member of the channel
var member_list = this.state.member_list; let memberList = this.state.memberList;
var found = false; let found = false;
for (var i = 0; i < member_list.length; i++) { for (let i = 0; i < memberList.length; i++) {
if (member_list[i].id === user_id) { if (memberList[i].id === userId) {
found = true; found = true;
break; break;
} }
} }
if (!found) { return }; if (!found) {
return;
}
var data = {}; let data = {};
data['user_id'] = user_id; data.user_id = userId;
client.removeChannelMember(ChannelStore.getCurrentId(), data, Client.removeChannelMember(ChannelStore.getCurrentId(), data,
function(data) { function handleRemoveSuccess() {
var old_member; let oldMember;
for (var i = 0; i < member_list.length; i++) { for (let i = 0; i < memberList.length; i++) {
if (user_id === member_list[i].id) { if (userId === memberList[i].id) {
old_member = member_list[i]; oldMember = memberList[i];
member_list.splice(i, 1); memberList.splice(i, 1);
break; break;
} }
} }
var nonmember_list = this.state.nonmember_list; let nonmemberList = this.state.nonmemberList;
if (old_member) { if (oldMember) {
nonmember_list.push(old_member); nonmemberList.push(oldMember);
} }
this.setState({ member_list: member_list, nonmember_list: nonmember_list }); this.setState({memberList: memberList, nonmemberList: nonmemberList});
AsyncClient.getChannelExtraInfo(true); AsyncClient.getChannelExtraInfo(true);
}.bind(this), }.bind(this),
function(err) { function handleRemoveError(err) {
this.setState({ invite_error: err.message }); this.setState({inviteError: err.message});
}.bind(this) }.bind(this)
); );
}, }
getInitialState: function() { render() {
return getStateFromStores(); const currentMember = ChannelStore.getCurrentMember();
}, let isAdmin = false;
render: function() {
var currentMember = ChannelStore.getCurrentMember();
var isAdmin = false;
if (currentMember) { if (currentMember) {
isAdmin = currentMember.roles.indexOf("admin") > -1 || UserStore.getCurrentUser().roles.indexOf("admin") > -1; isAdmin = currentMember.roles.indexOf('admin') > -1 || UserStore.getCurrentUser().roles.indexOf('admin') > -1;
}
var memberList = null;
if (this.state.renderMembers) {
memberList = (
<MemberList
memberList={this.state.memberList}
isAdmin={isAdmin}
handleRemove={this.handleRemove}
/>
);
} }
return ( return (
<div className="modal fade" ref="modal" id="channel_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='channel_members'
<button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> tabIndex='-1'
<h4 className="modal-title"><span className="name">{this.state.channel_name}</span> Members</h4> role='dialog'
<a className="btn btn-md btn-primary" data-toggle="modal" data-target="#channel_invite"><i className="glyphicon glyphicon-envelope"/> Add New Members</a> aria-hidden='true'
</div> >
<div ref="modalBody" className="modal-body"> <div className='modal-dialog'>
<div className="col-sm-12"> <div className='modal-content'>
<div className="team-member-list"> <div className='modal-header'>
{ this.state.render_members ? <button
<MemberList type='button'
memberList={this.state.member_list} className='close'
isAdmin={isAdmin} data-dismiss='modal'
handleRemove={this.handleRemove} aria-label='Close'
/> >
: "" } <span aria-hidden='true'>×</span>
</button>
<h4 className='modal-title'><span className='name'>{this.state.channelName}</span> Members</h4>
<a
className='btn btn-md btn-primary'
data-toggle='modal'
data-target='#channel_invite'
>
<i className='glyphicon glyphicon-envelope'/> Add New Members
</a>
</div>
<div
ref='modalBody'
className='modal-body'
>
<div className='col-sm-12'>
<div className='team-member-list'>
{memberList}
</div>
</div> </div>
</div> </div>
<div className='modal-footer'>
<button
type='button'
className='btn btn-default'
data-dismiss='modal'
>
Close
</button>
</div>
</div> </div>
<div className="modal-footer"> </div>
<button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div> </div>
); );
} }
}); }

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

@@ -1,24 +1,48 @@
// 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 AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var client = require('../utils/client.jsx'); const Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx'); const AsyncClient = require('../utils/async_client.jsx');
var SocketStore = require('../stores/socket_store.jsx'); const SocketStore = require('../stores/socket_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx'); const ChannelStore = require('../stores/channel_store.jsx');
var UserStore = require('../stores/user_store.jsx'); const UserStore = require('../stores/user_store.jsx');
var PostStore = require('../stores/post_store.jsx'); const PostStore = require('../stores/post_store.jsx');
var Textbox = require('./textbox.jsx'); const Textbox = require('./textbox.jsx');
var MsgTyping = require('./msg_typing.jsx'); const MsgTyping = require('./msg_typing.jsx');
var FileUpload = require('./file_upload.jsx'); const FileUpload = require('./file_upload.jsx');
var FilePreview = require('./file_preview.jsx'); const FilePreview = require('./file_preview.jsx');
var utils = require('../utils/utils.jsx'); const Utils = require('../utils/utils.jsx');
var Constants = require('../utils/constants.jsx'); const Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes; const ActionTypes = Constants.ActionTypes;
module.exports = React.createClass({ export default class CreateComment extends React.Component {
lastTime: 0, constructor(props) {
handleSubmit: function(e) { super(props);
this.lastTime = 0;
this.handleSubmit = this.handleSubmit.bind(this);
this.commentMsgKeyPress = this.commentMsgKeyPress.bind(this);
this.handleUserInput = this.handleUserInput.bind(this);
this.handleUploadStart = this.handleUploadStart.bind(this);
this.handleFileUploadComplete = this.handleFileUploadComplete.bind(this);
this.handleUploadError = this.handleUploadError.bind(this);
this.removePreview = this.removePreview.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.getFileCount = this.getFileCount.bind(this);
PostStore.clearCommentDraftUploads();
const draft = PostStore.getCommentDraft(this.props.rootId);
this.state = {
messageText: draft.message,
uploadsInProgress: draft.uploadsInProgress,
previews: draft.previews,
submitting: false
};
}
handleSubmit(e) {
e.preventDefault(); e.preventDefault();
if (this.state.uploadsInProgress.length > 0) { if (this.state.uploadsInProgress.length > 0) {
@@ -29,7 +53,7 @@ module.exports = React.createClass({
return; return;
} }
var post = {}; let post = {};
post.filenames = []; post.filenames = [];
post.message = this.state.messageText; post.message = this.state.messageText;
@@ -38,30 +62,30 @@ module.exports = React.createClass({
} }
if (post.message.length > Constants.CHARACTER_LIMIT) { if (post.message.length > Constants.CHARACTER_LIMIT) {
this.setState({postError: 'Comment length must be less than ' + Constants.CHARACTER_LIMIT + ' characters.'}); this.setState({postError: `Comment length must be less than ${Constants.CHARACTER_LIMIT} characters.`});
return; return;
} }
var user_id = UserStore.getCurrentId(); const userId = UserStore.getCurrentId();
post.channel_id = this.props.channelId; post.channel_id = this.props.channelId;
post.root_id = this.props.rootId; post.root_id = this.props.rootId;
post.parent_id = this.props.rootId; post.parent_id = this.props.rootId;
post.filenames = this.state.previews; post.filenames = this.state.previews;
var time = utils.getTimestamp(); const time = Utils.getTimestamp();
post.pending_post_id = user_id + ':'+ time; post.pending_post_id = `${userId}:${time}`;
post.user_id = user_id; post.user_id = userId;
post.create_at = time; post.create_at = time;
PostStore.storePendingPost(post); PostStore.storePendingPost(post);
PostStore.storeCommentDraft(this.props.rootId, null); PostStore.storeCommentDraft(this.props.rootId, null);
client.createPost(post, ChannelStore.getCurrent(), Client.createPost(post, ChannelStore.getCurrent(),
function(data) { function handlePostSuccess(data) {
AsyncClient.getPosts(this.props.channelId); AsyncClient.getPosts(this.props.channelId);
var channel = ChannelStore.get(this.props.channelId); const channel = ChannelStore.get(this.props.channelId);
var member = ChannelStore.getMember(this.props.channelId); let member = ChannelStore.getMember(this.props.channelId);
member.msg_count = channel.total_msg_count; member.msg_count = channel.total_msg_count;
member.last_viewed_at = Date.now(); member.last_viewed_at = Date.now();
ChannelStore.setChannelMember(member); ChannelStore.setChannelMember(member);
@@ -71,8 +95,8 @@ module.exports = React.createClass({
post: data post: data
}); });
}.bind(this), }.bind(this),
function(err) { function handlePostError(err) {
var state = {}; let state = {};
if (err.message === 'Invalid RootId parameter') { if (err.message === 'Invalid RootId parameter') {
if ($('#post_deleted').length > 0) { if ($('#post_deleted').length > 0) {
@@ -90,76 +114,76 @@ module.exports = React.createClass({
); );
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null}); this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
}, }
commentMsgKeyPress: function(e) { commentMsgKeyPress(e) {
if (e.which === 13 && !e.shiftKey && !e.altKey) { if (e.which === 13 && !e.shiftKey && !e.altKey) {
e.preventDefault(); e.preventDefault();
this.refs.textbox.getDOMNode().blur(); React.findDOMNode(this.refs.textbox).blur();
this.handleSubmit(e); this.handleSubmit(e);
} }
var t = Date.now(); const t = Date.now();
if ((t - this.lastTime) > 5000) { if ((t - this.lastTime) > 5000) {
SocketStore.sendMessage({channel_id: this.props.channelId, action: 'typing', props: {'parent_id': this.props.rootId}}); SocketStore.sendMessage({channel_id: this.props.channelId, action: 'typing', props: {parent_id: this.props.rootId}});
this.lastTime = t; this.lastTime = t;
} }
}, }
handleUserInput: function(messageText) { handleUserInput(messageText) {
var draft = PostStore.getCommentDraft(this.props.rootId); let draft = PostStore.getCommentDraft(this.props.rootId);
draft.message = messageText; draft.message = messageText;
PostStore.storeCommentDraft(this.props.rootId, draft); PostStore.storeCommentDraft(this.props.rootId, draft);
$('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight); $('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight);
$('.post-right__scroll').perfectScrollbar('update'); $('.post-right__scroll').perfectScrollbar('update');
this.setState({messageText: messageText}); this.setState({messageText: messageText});
}, }
handleUploadStart: function(clientIds, channelId) { handleUploadStart(clientIds) {
var draft = PostStore.getCommentDraft(this.props.rootId); let draft = PostStore.getCommentDraft(this.props.rootId);
draft['uploadsInProgress'] = draft['uploadsInProgress'].concat(clientIds); draft.uploadsInProgress = draft.uploadsInProgress.concat(clientIds);
PostStore.storeCommentDraft(this.props.rootId, draft); PostStore.storeCommentDraft(this.props.rootId, draft);
this.setState({uploadsInProgress: draft['uploadsInProgress']}); this.setState({uploadsInProgress: draft.uploadsInProgress});
}, }
handleFileUploadComplete: function(filenames, clientIds, channelId) { handleFileUploadComplete(filenames, clientIds) {
var draft = PostStore.getCommentDraft(this.props.rootId); let draft = PostStore.getCommentDraft(this.props.rootId);
// remove each finished file from uploads // remove each finished file from uploads
for (var i = 0; i < clientIds.length; i++) { for (let i = 0; i < clientIds.length; i++) {
var index = draft['uploadsInProgress'].indexOf(clientIds[i]); const index = draft.uploadsInProgress.indexOf(clientIds[i]);
if (index !== -1) { if (index !== -1) {
draft['uploadsInProgress'].splice(index, 1); draft.uploadsInProgress.splice(index, 1);
} }
} }
draft['previews'] = draft['previews'].concat(filenames); draft.previews = draft.previews.concat(filenames);
PostStore.storeCommentDraft(this.props.rootId, draft); PostStore.storeCommentDraft(this.props.rootId, draft);
this.setState({uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews']}); this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews});
}, }
handleUploadError: function(err, clientId) { handleUploadError(err, clientId) {
if (clientId !== -1) { if (clientId !== -1) {
var draft = PostStore.getCommentDraft(this.props.rootId); let draft = PostStore.getCommentDraft(this.props.rootId);
var index = draft['uploadsInProgress'].indexOf(clientId); const index = draft.uploadsInProgress.indexOf(clientId);
if (index !== -1) { if (index !== -1) {
draft['uploadsInProgress'].splice(index, 1); draft.uploadsInProgress.splice(index, 1);
} }
PostStore.storeCommentDraft(this.props.rootId, draft); PostStore.storeCommentDraft(this.props.rootId, draft);
this.setState({uploadsInProgress: draft['uploadsInProgress'], serverError: err}); this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: err});
} else { } else {
this.setState({serverError: err}); this.setState({serverError: err});
} }
}, }
removePreview: function(id) { removePreview(id) {
var previews = this.state.previews; let previews = this.state.previews;
var uploadsInProgress = this.state.uploadsInProgress; let uploadsInProgress = this.state.uploadsInProgress;
// id can either be the path of an uploaded file or the client id of an in progress upload // id can either be the path of an uploaded file or the client id of an in progress upload
var index = previews.indexOf(id); let index = previews.indexOf(id);
if (index !== -1) { if (index !== -1) {
previews.splice(index, 1); previews.splice(index, 1);
} else { } else {
@@ -171,30 +195,24 @@ module.exports = React.createClass({
} }
} }
var draft = PostStore.getCommentDraft(this.props.rootId); let draft = PostStore.getCommentDraft(this.props.rootId);
draft.previews = previews; draft.previews = previews;
draft.uploadsInProgress = uploadsInProgress; draft.uploadsInProgress = uploadsInProgress;
PostStore.storeCommentDraft(this.props.rootId, draft); PostStore.storeCommentDraft(this.props.rootId, draft);
this.setState({previews: previews, uploadsInProgress: uploadsInProgress}); this.setState({previews: previews, uploadsInProgress: uploadsInProgress});
}, }
getInitialState: function() { componentWillReceiveProps(newProps) {
PostStore.clearCommentDraftUploads();
var draft = PostStore.getCommentDraft(this.props.rootId);
return {messageText: draft['message'], uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews'], submitting: false};
},
componentWillReceiveProps: function(newProps) {
if (newProps.rootId !== this.props.rootId) { if (newProps.rootId !== this.props.rootId) {
var draft = PostStore.getCommentDraft(newProps.rootId); const draft = PostStore.getCommentDraft(newProps.rootId);
this.setState({messageText: draft['message'], uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews']}); this.setState({messageText: draft.message, uploadsInProgress: draft.uploadsInProgress, previews: draft.previews});
} }
}, }
getFileCount: function(channelId) { getFileCount() {
return this.state.previews.length + this.state.uploadsInProgress.length; return this.state.previews.length + this.state.uploadsInProgress.length;
}, }
render: function() { render() {
var serverError = null; let serverError = null;
if (this.state.serverError) { if (this.state.serverError) {
serverError = ( serverError = (
<div className='form-group has-error'> <div className='form-group has-error'>
@@ -203,22 +221,23 @@ module.exports = React.createClass({
); );
} }
var postError = null; let postError = null;
if (this.state.postError) { if (this.state.postError) {
postError = <label className='control-label'>{this.state.postError}</label>; postError = <label className='control-label'>{this.state.postError}</label>;
} }
var preview = null; let preview = null;
if (this.state.previews.length > 0 || this.state.uploadsInProgress.length > 0) { if (this.state.previews.length > 0 || this.state.uploadsInProgress.length > 0) {
preview = ( preview = (
<FilePreview <FilePreview
files={this.state.previews} files={this.state.previews}
onRemove={this.removePreview} onRemove={this.removePreview}
uploadsInProgress={this.state.uploadsInProgress} /> uploadsInProgress={this.state.uploadsInProgress}
/>
); );
} }
var postFooterClassName = 'post-create-footer'; let postFooterClassName = 'post-create-footer';
if (postError) { if (postError) {
postFooterClassName += ' has-error'; postFooterClassName += ' has-error';
} }
@@ -226,7 +245,10 @@ module.exports = React.createClass({
return ( return (
<form onSubmit={this.handleSubmit}> <form onSubmit={this.handleSubmit}>
<div className='post-create'> <div className='post-create'>
<div id={this.props.rootId} className='post-create-body comment-create-body'> <div
id={this.props.rootId}
className='post-create-body comment-create-body'
>
<Textbox <Textbox
onUserInput={this.handleUserInput} onUserInput={this.handleUserInput}
onKeyPress={this.commentMsgKeyPress} onKeyPress={this.commentMsgKeyPress}
@@ -234,7 +256,8 @@ module.exports = React.createClass({
createMessage='Add a comment...' createMessage='Add a comment...'
initialText='' initialText=''
id='reply_textbox' id='reply_textbox'
ref='textbox' /> ref='textbox'
/>
<FileUpload <FileUpload
ref='fileUpload' ref='fileUpload'
getFileCount={this.getFileCount} getFileCount={this.getFileCount}
@@ -242,11 +265,20 @@ module.exports = React.createClass({
onFileUpload={this.handleFileUploadComplete} onFileUpload={this.handleFileUploadComplete}
onUploadError={this.handleUploadError} onUploadError={this.handleUploadError}
postType='comment' postType='comment'
channelId={this.props.channelId} /> channelId={this.props.channelId}
/>
</div> </div>
<MsgTyping channelId={this.props.channelId} parentId={this.props.rootId} /> <MsgTyping
channelId={this.props.channelId}
parentId={this.props.rootId}
/>
<div className={postFooterClassName}> <div className={postFooterClassName}>
<input type='button' className='btn btn-primary comment-btn pull-right' value='Add Comment' onClick={this.handleSubmit} /> <input
type='button'
className='btn btn-primary comment-btn pull-right'
value='Add Comment'
onClick={this.handleSubmit}
/>
{postError} {postError}
{serverError} {serverError}
</div> </div>
@@ -255,4 +287,9 @@ module.exports = React.createClass({
</form> </form>
); );
} }
}); }
CreateComment.propTypes = {
channelId: React.PropTypes.string.isRequired,
rootId: React.PropTypes.string.isRequired
};

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

@@ -1,33 +1,68 @@
// 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 AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var client = require('../utils/client.jsx'); const Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx'); const AsyncClient = require('../utils/async_client.jsx');
var ChannelStore = require('../stores/channel_store.jsx'); const ChannelStore = require('../stores/channel_store.jsx');
var PostStore = require('../stores/post_store.jsx'); const PostStore = require('../stores/post_store.jsx');
var UserStore = require('../stores/user_store.jsx'); const UserStore = require('../stores/user_store.jsx');
var SocketStore = require('../stores/socket_store.jsx'); const SocketStore = require('../stores/socket_store.jsx');
var MsgTyping = require('./msg_typing.jsx'); const MsgTyping = require('./msg_typing.jsx');
var Textbox = require('./textbox.jsx'); const Textbox = require('./textbox.jsx');
var FileUpload = require('./file_upload.jsx'); const FileUpload = require('./file_upload.jsx');
var FilePreview = require('./file_preview.jsx'); const FilePreview = require('./file_preview.jsx');
var utils = require('../utils/utils.jsx'); const Utils = require('../utils/utils.jsx');
var Constants = require('../utils/constants.jsx'); const Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes; const ActionTypes = Constants.ActionTypes;
module.exports = React.createClass({ export default class CreatePost extends React.Component {
displayName: 'CreatePost', constructor(props) {
lastTime: 0, super(props);
handleSubmit: function(e) {
this.lastTime = 0;
this.handleSubmit = this.handleSubmit.bind(this);
this.postMsgKeyPress = this.postMsgKeyPress.bind(this);
this.handleUserInput = this.handleUserInput.bind(this);
this.resizePostHolder = this.resizePostHolder.bind(this);
this.handleUploadStart = this.handleUploadStart.bind(this);
this.handleFileUploadComplete = this.handleFileUploadComplete.bind(this);
this.handleUploadError = this.handleUploadError.bind(this);
this.removePreview = this.removePreview.bind(this);
this.onChange = this.onChange.bind(this);
this.getFileCount = this.getFileCount.bind(this);
PostStore.clearDraftUploads();
const draft = PostStore.getCurrentDraft();
let previews = [];
let messageText = '';
let uploadsInProgress = [];
if (draft && draft.previews && draft.message) {
previews = draft.previews;
messageText = draft.message;
uploadsInProgress = draft.uploadsInProgress;
}
this.state = {
channelId: ChannelStore.getCurrentId(),
messageText: messageText,
uploadsInProgress: uploadsInProgress,
previews: previews,
submitting: false,
initialText: messageText
};
}
handleSubmit(e) {
e.preventDefault(); e.preventDefault();
if (this.state.uploadsInProgress.length > 0 || this.state.submitting) { if (this.state.uploadsInProgress.length > 0 || this.state.submitting) {
return; return;
} }
var post = {}; let post = {};
post.filenames = []; post.filenames = [];
post.message = this.state.messageText; post.message = this.state.messageText;
@@ -36,18 +71,18 @@ module.exports = React.createClass({
} }
if (post.message.length > Constants.CHARACTER_LIMIT) { if (post.message.length > Constants.CHARACTER_LIMIT) {
this.setState({postError: 'Post length must be less than ' + Constants.CHARACTER_LIMIT + ' characters.'}); this.setState({postError: `Post length must be less than ${Constants.CHARACTER_LIMIT} characters.`});
return; return;
} }
this.setState({submitting: true, serverError: null}); this.setState({submitting: true, serverError: null});
if (post.message.indexOf('/') === 0) { if (post.message.indexOf('/') === 0) {
client.executeCommand( Client.executeCommand(
this.state.channelId, this.state.channelId,
post.message, post.message,
false, false,
function(data) { function handleCommandSuccess(data) {
PostStore.storeDraft(data.channel_id, null); PostStore.storeDraft(data.channel_id, null);
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null}); this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
@@ -55,8 +90,8 @@ module.exports = React.createClass({
window.location.href = data.goto_location; window.location.href = data.goto_location;
} }
}.bind(this), }.bind(this),
function(err) { function handleCommandError(err) {
var state = {}; let state = {};
state.serverError = err.message; state.serverError = err.message;
state.submitting = false; state.submitting = false;
this.setState(state); this.setState(state);
@@ -66,26 +101,26 @@ module.exports = React.createClass({
post.channel_id = this.state.channelId; post.channel_id = this.state.channelId;
post.filenames = this.state.previews; post.filenames = this.state.previews;
var time = utils.getTimestamp(); const time = Utils.getTimestamp();
var userId = UserStore.getCurrentId(); const userId = UserStore.getCurrentId();
post.pending_post_id = userId + ':' + time; post.pending_post_id = `${userId}:${time}`;
post.user_id = userId; post.user_id = userId;
post.create_at = time; post.create_at = time;
post.root_id = this.state.rootId; post.root_id = this.state.rootId;
post.parent_id = this.state.parentId; post.parent_id = this.state.parentId;
var channel = ChannelStore.get(this.state.channelId); const channel = ChannelStore.get(this.state.channelId);
PostStore.storePendingPost(post); PostStore.storePendingPost(post);
PostStore.storeDraft(channel.id, null); PostStore.storeDraft(channel.id, null);
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null}); this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
client.createPost(post, channel, Client.createPost(post, channel,
function(data) { function handlePostSuccess(data) {
this.resizePostHolder(); this.resizePostHolder();
AsyncClient.getPosts(); AsyncClient.getPosts();
var member = ChannelStore.getMember(channel.id); let member = ChannelStore.getMember(channel.id);
member.msg_count = channel.total_msg_count; member.msg_count = channel.total_msg_count;
member.last_viewed_at = Date.now(); member.last_viewed_at = Date.now();
ChannelStore.setChannelMember(member); ChannelStore.setChannelMember(member);
@@ -95,8 +130,8 @@ module.exports = React.createClass({
post: data post: data
}); });
}.bind(this), }.bind(this),
function(err) { function handlePostError(err) {
var state = {}; let state = {};
if (err.message === 'Invalid RootId parameter') { if (err.message === 'Invalid RootId parameter') {
if ($('#post_deleted').length > 0) { if ($('#post_deleted').length > 0) {
@@ -113,83 +148,83 @@ module.exports = React.createClass({
}.bind(this) }.bind(this)
); );
} }
}, }
componentDidUpdate: function() { componentDidUpdate() {
this.resizePostHolder(); this.resizePostHolder();
}, }
postMsgKeyPress: function(e) { postMsgKeyPress(e) {
if (e.which === 13 && !e.shiftKey && !e.altKey) { if (e.which === 13 && !e.shiftKey && !e.altKey) {
e.preventDefault(); e.preventDefault();
this.refs.textbox.getDOMNode().blur(); React.findDOMNode(this.refs.textbox).blur();
this.handleSubmit(e); this.handleSubmit(e);
} }
var t = Date.now(); const t = Date.now();
if ((t - this.lastTime) > 5000) { if ((t - this.lastTime) > 5000) {
SocketStore.sendMessage({channel_id: this.state.channelId, action: 'typing', props: {'parent_id': ''}, state: {}}); SocketStore.sendMessage({channel_id: this.state.channelId, action: 'typing', props: {parent_id: ''}, state: {}});
this.lastTime = t; this.lastTime = t;
} }
}, }
handleUserInput: function(messageText) { handleUserInput(messageText) {
this.resizePostHolder(); this.resizePostHolder();
this.setState({messageText: messageText}); this.setState({messageText: messageText});
var draft = PostStore.getCurrentDraft(); let draft = PostStore.getCurrentDraft();
draft['message'] = messageText; draft.message = messageText;
PostStore.storeCurrentDraft(draft); PostStore.storeCurrentDraft(draft);
}, }
resizePostHolder: function() { resizePostHolder() {
var height = $(window).height() - $(this.refs.topDiv.getDOMNode()).height() - $('#error_bar').outerHeight() - 50; const height = $(window).height() - $(React.findDOMNode(this.refs.topDiv)).height() - $('#error_bar').outerHeight() - 50;
$('.post-list-holder-by-time').css('height', height + 'px'); $('.post-list-holder-by-time').css('height', `${height}px`);
$(window).trigger('resize'); $(window).trigger('resize');
}, }
handleUploadStart: function(clientIds, channelId) { handleUploadStart(clientIds, channelId) {
var draft = PostStore.getDraft(channelId); let draft = PostStore.getDraft(channelId);
draft['uploadsInProgress'] = draft['uploadsInProgress'].concat(clientIds); draft.uploadsInProgress = draft.uploadsInProgress.concat(clientIds);
PostStore.storeDraft(channelId, draft); PostStore.storeDraft(channelId, draft);
this.setState({uploadsInProgress: draft['uploadsInProgress']}); this.setState({uploadsInProgress: draft.uploadsInProgress});
}, }
handleFileUploadComplete: function(filenames, clientIds, channelId) { handleFileUploadComplete(filenames, clientIds, channelId) {
var draft = PostStore.getDraft(channelId); let draft = PostStore.getDraft(channelId);
// remove each finished file from uploads // remove each finished file from uploads
for (var i = 0; i < clientIds.length; i++) { for (let i = 0; i < clientIds.length; i++) {
var index = draft['uploadsInProgress'].indexOf(clientIds[i]); const index = draft.uploadsInProgress.indexOf(clientIds[i]);
if (index !== -1) { if (index !== -1) {
draft['uploadsInProgress'].splice(index, 1); draft.uploadsInProgress.splice(index, 1);
} }
} }
draft['previews'] = draft['previews'].concat(filenames); draft.previews = draft.previews.concat(filenames);
PostStore.storeDraft(channelId, draft); PostStore.storeDraft(channelId, draft);
this.setState({uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews']}); this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews});
}, }
handleUploadError: function(err, clientId) { handleUploadError(err, clientId) {
if (clientId !== -1) { if (clientId !== -1) {
var draft = PostStore.getDraft(this.state.channelId); let draft = PostStore.getDraft(this.state.channelId);
var index = draft['uploadsInProgress'].indexOf(clientId); const index = draft.uploadsInProgress.indexOf(clientId);
if (index !== -1) { if (index !== -1) {
draft['uploadsInProgress'].splice(index, 1); draft.uploadsInProgress.splice(index, 1);
} }
PostStore.storeDraft(this.state.channelId, draft); PostStore.storeDraft(this.state.channelId, draft);
this.setState({uploadsInProgress: draft['uploadsInProgress'], serverError: err}); this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: err});
} else { } else {
this.setState({serverError: err}); this.setState({serverError: err});
} }
}, }
removePreview: function(id) { removePreview(id) {
var previews = this.state.previews; let previews = this.state.previews;
var uploadsInProgress = this.state.uploadsInProgress; let uploadsInProgress = this.state.uploadsInProgress;
// id can either be the path of an uploaded file or the client id of an in progress upload // id can either be the path of an uploaded file or the client id of an in progress upload
var index = previews.indexOf(id); let index = previews.indexOf(id);
if (index !== -1) { if (index !== -1) {
previews.splice(index, 1); previews.splice(index, 1);
} else { } else {
@@ -201,28 +236,28 @@ module.exports = React.createClass({
} }
} }
var draft = PostStore.getCurrentDraft(); let draft = PostStore.getCurrentDraft();
draft['previews'] = previews; draft.previews = previews;
draft['uploadsInProgress'] = uploadsInProgress; draft.uploadsInProgress = uploadsInProgress;
PostStore.storeCurrentDraft(draft); PostStore.storeCurrentDraft(draft);
this.setState({previews: previews, uploadsInProgress: uploadsInProgress}); this.setState({previews: previews, uploadsInProgress: uploadsInProgress});
}, }
componentDidMount: function() { componentDidMount() {
ChannelStore.addChangeListener(this._onChange); ChannelStore.addChangeListener(this.onChange);
this.resizePostHolder(); this.resizePostHolder();
}, }
componentWillUnmount: function() { componentWillUnmount() {
ChannelStore.removeChangeListener(this._onChange); ChannelStore.removeChangeListener(this.onChange);
}, }
_onChange: function() { onChange() {
var channelId = ChannelStore.getCurrentId(); const channelId = ChannelStore.getCurrentId();
if (this.state.channelId !== channelId) { if (this.state.channelId !== channelId) {
var draft = PostStore.getCurrentDraft(); let draft = PostStore.getCurrentDraft();
var previews = []; let previews = [];
var messageText = ''; let messageText = '';
var uploadsInProgress = []; let uploadsInProgress = [];
if (draft && draft.previews && draft.message) { if (draft && draft.previews && draft.message) {
previews = draft.previews; previews = draft.previews;
messageText = draft.message; messageText = draft.message;
@@ -231,33 +266,17 @@ module.exports = React.createClass({
this.setState({channelId: channelId, messageText: messageText, initialText: messageText, submitting: false, serverError: null, postError: null, previews: previews, uploadsInProgress: uploadsInProgress}); this.setState({channelId: channelId, messageText: messageText, initialText: messageText, submitting: false, serverError: null, postError: null, previews: previews, uploadsInProgress: uploadsInProgress});
} }
}, }
getInitialState: function() { getFileCount(channelId) {
PostStore.clearDraftUploads();
var draft = PostStore.getCurrentDraft();
var previews = [];
var messageText = '';
var uploadsInProgress = [];
if (draft && draft.previews && draft.message) {
previews = draft.previews;
messageText = draft.message;
uploadsInProgress = draft.uploadsInProgress;
}
return {channelId: ChannelStore.getCurrentId(), messageText: messageText, uploadsInProgress: uploadsInProgress, previews: previews, submitting: false, initialText: messageText};
},
getFileCount: function(channelId) {
if (channelId === this.state.channelId) { if (channelId === this.state.channelId) {
return this.state.previews.length + this.state.uploadsInProgress.length; return this.state.previews.length + this.state.uploadsInProgress.length;
} else {
var draft = PostStore.getDraft(channelId);
return draft['previews'].length + draft['uploadsInProgress'].length;
} }
},
render: function() { const draft = PostStore.getDraft(channelId);
var serverError = null; return draft.previews.length + draft.uploadsInProgress.length;
}
render() {
let serverError = null;
if (this.state.serverError) { if (this.state.serverError) {
serverError = ( serverError = (
<div className='has-error'> <div className='has-error'>
@@ -266,12 +285,12 @@ module.exports = React.createClass({
); );
} }
var postError = null; let postError = null;
if (this.state.postError) { if (this.state.postError) {
postError = <label className='control-label'>{this.state.postError}</label>; postError = <label className='control-label'>{this.state.postError}</label>;
} }
var preview = null; let preview = null;
if (this.state.previews.length > 0 || this.state.uploadsInProgress.length > 0) { if (this.state.previews.length > 0 || this.state.uploadsInProgress.length > 0) {
preview = ( preview = (
<FilePreview <FilePreview
@@ -281,13 +300,18 @@ module.exports = React.createClass({
); );
} }
var postFooterClassName = 'post-create-footer'; let postFooterClassName = 'post-create-footer';
if (postError) { if (postError) {
postFooterClassName += ' has-error'; postFooterClassName += ' has-error';
} }
return ( return (
<form id='create_post' ref='topDiv' role='form' onSubmit={this.handleSubmit}> <form
id='create_post'
ref='topDiv'
role='form'
onSubmit={this.handleSubmit}
>
<div className='post-create'> <div className='post-create'>
<div className='post-create-body'> <div className='post-create-body'>
<Textbox <Textbox
@@ -311,10 +335,13 @@ module.exports = React.createClass({
{postError} {postError}
{serverError} {serverError}
{preview} {preview}
<MsgTyping channelId={this.state.channelId} parentId=''/> <MsgTyping
channelId={this.state.channelId}
parentId=''
/>
</div> </div>
</div> </div>
</form> </form>
); );
} }
}); }

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

@@ -1,58 +1,99 @@
// 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 Client =require('../utils/client.jsx'); const Client = require('../utils/client.jsx');
var AsyncClient =require('../utils/async_client.jsx'); const AsyncClient = require('../utils/async_client.jsx');
var ChannelStore =require('../stores/channel_store.jsx') const ChannelStore = require('../stores/channel_store.jsx');
module.exports = React.createClass({ export default class DeleteChannelModal extends React.Component {
handleDelete: function(e) { constructor(props) {
if (this.state.channel_id.length != 26) return; super(props);
Client.deleteChannel(this.state.channel_id, this.handleDelete = this.handleDelete.bind(this);
function(data) {
this.state = {
title: '',
channelId: ''
};
}
handleDelete() {
if (this.state.channelId.length !== 26) {
return;
}
Client.deleteChannel(this.state.channelId,
function handleDeleteSuccess() {
AsyncClient.getChannels(true); AsyncClient.getChannels(true);
window.location.href = '/'; window.location.href = '/';
}.bind(this), },
function(err) { function handleDeleteError(err) {
AsyncClient.dispatchError(err, "handleDelete"); AsyncClient.dispatchError(err, 'handleDelete');
}.bind(this) }
); );
}, }
componentDidMount: function() { componentDidMount() {
var self = this; $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) {
$(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
var button = $(e.relatedTarget); var button = $(e.relatedTarget);
self.setState({ title: button.attr('data-title'), channel_id: button.attr('data-channelid') }); this.setState({
}); title: button.attr('data-title'),
}, channelId: button.attr('data-channelid')
getInitialState: function() { });
return { title: "", channel_id: "" }; }.bind(this));
}, }
render: function() { render() {
const channel = ChannelStore.getCurrent();
var channelType = ChannelStore.getCurrent() && ChannelStore.getCurrent().type === 'P' ? "private group" : "channel" let channelType = 'channel';
if (channel && channel.type === 'P') {
channelType = 'private group';
}
return ( return (
<div className="modal fade" ref="modal" id="delete_channel" role="dialog" tabIndex="-1" aria-hidden="true"> <div
<div className="modal-dialog"> className='modal fade'
<div className="modal-content"> ref='modal'
<div className="modal-header"> id='delete_channel'
<button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button> role='dialog'
<h4 className="modal-title">Confirm DELETE Channel</h4> tabIndex='-1'
</div> aria-hidden='true'
<div className="modal-body"> >
<p> <div className='modal-dialog'>
Are you sure you wish to delete the {this.state.title} {channelType}? <div className='modal-content'>
</p> <div className='modal-header'>
</div> <button
<div className="modal-footer"> type='button'
<button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button> className='close'
<button type="button" className="btn btn-danger" data-dismiss="modal" onClick={this.handleDelete}>Delete</button> data-dismiss='modal'
</div> aria-label='Close'
>
<span aria-hidden='true'>&times;</span>
</button>
<h4 className='modal-title'>Confirm DELETE Channel</h4>
</div>
<div className='modal-body'>
<p>
Are you sure you wish to delete the {this.state.title} {channelType}?
</p>
</div>
<div className='modal-footer'>
<button
type='button'
className='btn btn-default'
data-dismiss='modal'
>
Cancel
</button>
<button
type='button'
className='btn btn-danger'
data-dismiss='modal'
onClick={this.handleDelete}
>
Delete
</button>
</div>
</div>
</div> </div>
</div>
</div> </div>
); );
} }
}); }

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

@@ -1,79 +1,142 @@
// 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 Client = require('../utils/client.jsx'); const Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx'); const AsyncClient = require('../utils/async_client.jsx');
module.exports = React.createClass({ export default class EditChannelModal extends React.Component {
handleEdit: function(e) { constructor(props) {
super(props);
this.handleEdit = this.handleEdit.bind(this);
this.handleUserInput = this.handleUserInput.bind(this);
this.handleClose = this.handleClose.bind(this);
this.state = {
description: '',
title: '',
channelId: '',
serverError: ''
};
}
handleEdit() {
var data = {}; var data = {};
data["channel_id"] = this.state.channel_id; data.channel_id = this.state.channelId;
if (data["channel_id"].length !== 26) return;
data["channel_description"] = this.state.description.trim(); if (data.channel_id.length !== 26) {
return;
}
data.channel_description = this.state.description.trim();
Client.updateChannelDesc(data, Client.updateChannelDesc(data,
function(data) { function handleUpdateSuccess() {
this.setState({ server_error: "" }); this.setState({serverError: ''});
AsyncClient.getChannel(this.state.channel_id); AsyncClient.getChannel(this.state.channelId);
$(this.refs.modal.getDOMNode()).modal('hide'); $(React.findDOMNode(this.refs.modal)).modal('hide');
}.bind(this), }.bind(this),
function(err) { function handleUpdateError(err) {
if (err.message === "Invalid channel_description parameter") { if (err.message === 'Invalid channel_description parameter') {
this.setState({ server_error: "This description is too long, please enter a shorter one" }); this.setState({serverError: 'This description is too long, please enter a shorter one'});
} } else {
else { this.setState({serverError: err.message});
this.setState({ server_error: err.message });
} }
}.bind(this) }.bind(this)
); );
}, }
handleUserInput: function(e) { handleUserInput(e) {
this.setState({ description: e.target.value }); this.setState({description: e.target.value});
}, }
handleClose: function() { handleClose() {
this.setState({description: "", server_error: ""}); this.setState({description: '', serverError: ''});
}, }
componentDidMount: function() { componentDidMount() {
var self = this; $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) {
$(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { const button = e.relatedTarget;
var button = e.relatedTarget; this.setState({description: $(button).attr('data-desc'), title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), serverError: ''});
self.setState({ description: $(button).attr('data-desc'), title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), server_error: "" }); }.bind(this));
}); $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose);
$(this.refs.modal.getDOMNode()).on('hidden.bs.modal', this.handleClose) }
}, componentWillUnmount() {
componentWillUnmount: function() { $(React.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose);
$(this.refs.modal.getDOMNode()).off('hidden.bs.modal', this.handleClose) }
}, render() {
getInitialState: function() { var serverError = null;
return { description: "", title: "", channel_id: "" }; if (this.state.serverError) {
}, serverError = <div className='form-group has-error'><br/><label className='control-label'>{this.state.serverError}</label></div>;
render: function() { }
var server_error = this.state.server_error ? <div className='form-group has-error'><br/><label className='control-label'>{ this.state.server_error }</label></div> : null;
var editTitle = <h4 className='modal-title' ref='title'>Edit Description</h4>; var editTitle = (
<h4
className='modal-title'
ref='title'
>
Edit Description
</h4>
);
if (this.state.title) { if (this.state.title) {
editTitle = <h4 className='modal-title' ref='title'>Edit Description for <span className='name'>{this.state.title}</span></h4>; editTitle = (
<h4
className='modal-title'
ref='title'
>
Edit Description for <span className='name'>{this.state.title}</span>
</h4>
);
} }
return ( return (
<div className="modal fade" ref="modal" id="edit_channel" role="dialog" tabIndex="-1" aria-hidden="true"> <div
<div className="modal-dialog"> className='modal fade'
<div className="modal-content"> ref='modal'
<div className="modal-header"> id='edit_channel'
<button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button> role='dialog'
{editTitle} tabIndex='-1'
</div> aria-hidden='true'
<div className="modal-body"> >
<textarea className="form-control no-resize" rows="6" ref="channelDesc" maxLength="1024" value={this.state.description} onChange={this.handleUserInput}></textarea> <div className='modal-dialog'>
{ server_error } <div className='modal-content'>
</div> <div className='modal-header'>
<div className="modal-footer"> <button
<button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button> type='button'
<button type="button" className="btn btn-primary" onClick={this.handleEdit}>Save</button> className='close'
</div> data-dismiss='modal'
aria-label='Close'
>
<span aria-hidden='true'>&times;</span>
</button>
{editTitle}
</div>
<div className='modal-body'>
<textarea
className='form-control no-resize'
rows='6'
ref='channelDesc'
maxLength='1024'
value={this.state.description}
onChange={this.handleUserInput}
/>
{serverError}
</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>
</div> </div>
); );
} }
}); }

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

@@ -1,12 +1,8 @@
// 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 FileUploadOverlay extends React.Component {
displayName: 'FileUploadOverlay', render() {
propTypes: {
overlayType: React.PropTypes.string
},
render: function() {
var overlayClass = 'file-overlay hidden'; var overlayClass = 'file-overlay hidden';
if (this.props.overlayType === 'right') { if (this.props.overlayType === 'right') {
overlayClass += ' right-file-overlay'; overlayClass += ' right-file-overlay';
@@ -23,4 +19,8 @@ module.exports = React.createClass({
</div> </div>
); );
} }
}); }
FileUploadOverlay.propTypes = {
overlayType: React.PropTypes.string
};

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

@@ -1,11 +1,11 @@
// 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'); const Utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx'); const Client = require('../utils/client.jsx');
var UserStore = require('../stores/user_store.jsx'); const UserStore = require('../stores/user_store.jsx');
var BrowserStore = require('../stores/browser_store.jsx'); const BrowserStore = require('../stores/browser_store.jsx');
var Constants = require('../utils/constants.jsx'); const Constants = require('../utils/constants.jsx');
export default class Login extends React.Component { export default class Login extends React.Component {
constructor(props) { constructor(props) {
@@ -17,23 +17,23 @@ export default class Login extends React.Component {
} }
handleSubmit(e) { handleSubmit(e) {
e.preventDefault(); e.preventDefault();
var state = {}; let state = {};
var name = this.props.teamName; const name = this.props.teamName;
if (!name) { if (!name) {
state.serverError = 'Bad team name'; state.serverError = 'Bad team name';
this.setState(state); this.setState(state);
return; return;
} }
var email = this.refs.email.getDOMNode().value.trim(); const email = React.findDOMNode(this.refs.email).value.trim();
if (!email) { if (!email) {
state.serverError = 'An email is required'; state.serverError = 'An email is required';
this.setState(state); this.setState(state);
return; return;
} }
var password = this.refs.password.getDOMNode().value.trim(); const password = React.findDOMNode(this.refs.password).value.trim();
if (!password) { if (!password) {
state.serverError = 'A password is required'; state.serverError = 'A password is required';
this.setState(state); this.setState(state);
@@ -49,12 +49,12 @@ export default class Login extends React.Component {
state.serverError = ''; state.serverError = '';
this.setState(state); this.setState(state);
client.loginByEmail(name, email, password, Client.loginByEmail(name, email, password,
function loggedIn(data) { function loggedIn(data) {
UserStore.setCurrentUser(data); UserStore.setCurrentUser(data);
UserStore.setLastEmail(email); UserStore.setLastEmail(email);
var redirect = utils.getUrlParameter('redirect'); const redirect = Utils.getUrlParameter('redirect');
if (redirect) { if (redirect) {
window.location.href = decodeURIComponent(redirect); window.location.href = decodeURIComponent(redirect);
} else { } else {
@@ -73,31 +73,31 @@ export default class Login extends React.Component {
); );
} }
render() { render() {
var serverError; let serverError;
if (this.state.serverError) { if (this.state.serverError) {
serverError = <label className='control-label'>{this.state.serverError}</label>; serverError = <label className='control-label'>{this.state.serverError}</label>;
} }
var priorEmail = UserStore.getLastEmail(); let priorEmail = UserStore.getLastEmail();
var emailParam = utils.getUrlParameter('email'); const emailParam = Utils.getUrlParameter('email');
if (emailParam) { if (emailParam) {
priorEmail = decodeURIComponent(emailParam); priorEmail = decodeURIComponent(emailParam);
} }
var teamDisplayName = this.props.teamDisplayName; const teamDisplayName = this.props.teamDisplayName;
var teamName = this.props.teamName; const teamName = this.props.teamName;
var focusEmail = false; let focusEmail = false;
var focusPassword = false; let focusPassword = false;
if (priorEmail !== '') { if (priorEmail !== '') {
focusPassword = true; focusPassword = true;
} else { } else {
focusEmail = true; focusEmail = true;
} }
var authServices = JSON.parse(this.props.authServices); const authServices = JSON.parse(this.props.authServices);
var loginMessage = []; let loginMessage = [];
if (authServices.indexOf(Constants.GITLAB_SERVICE) !== -1) { if (authServices.indexOf(Constants.GITLAB_SERVICE) !== -1) {
loginMessage.push( loginMessage.push(
<a <a
@@ -110,12 +110,12 @@ export default class Login extends React.Component {
); );
} }
var errorClass = ''; let errorClass = '';
if (serverError) { if (serverError) {
errorClass = ' has-error'; errorClass = ' has-error';
} }
var emailSignup; let emailSignup;
if (authServices.indexOf(Constants.EMAIL_SERVICE) !== -1) { if (authServices.indexOf(Constants.EMAIL_SERVICE) !== -1) {
emailSignup = ( emailSignup = (
<div> <div>
@@ -163,7 +163,7 @@ export default class Login extends React.Component {
); );
} }
var forgotPassword; let forgotPassword;
if (emailSignup) { if (emailSignup) {
forgotPassword = ( forgotPassword = (
<div className='form-group'> <div className='form-group'>

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

@@ -1,122 +1,27 @@
// 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 ChannelStore = require('../stores/channel_store.jsx'); const MemberListTeamItem = require('./member_list_team_item.jsx');
var UserStore = require('../stores/user_store.jsx');
var Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var utils = require('../utils/utils.jsx');
var MemberListTeamItem = React.createClass({ export default class MemberListTeam extends React.Component {
handleMakeMember: function() { render() {
var data = {}; const memberList = this.props.users.map(function makeListItem(user) {
data["user_id"] = this.props.user.id; return (
data["new_roles"] = ""; <MemberListTeamItem
key={user.id}
Client.updateRoles(data, user={user}
function(data) { />
AsyncClient.getProfiles(); );
}.bind(this), }, this);
function(err) {
this.setState({ server_error: err.message });
}.bind(this)
);
},
handleMakeActive: function() {
Client.updateActive(this.props.user.id, true,
function(data) {
AsyncClient.getProfiles();
}.bind(this),
function(err) {
this.setState({ server_error: err.message });
}.bind(this)
);
},
handleMakeNotActive: function() {
Client.updateActive(this.props.user.id, false,
function(data) {
AsyncClient.getProfiles();
}.bind(this),
function(err) {
this.setState({ server_error: err.message });
}.bind(this)
);
},
handleMakeAdmin: function() {
var data = {};
data["user_id"] = this.props.user.id;
data["new_roles"] = "admin";
Client.updateRoles(data,
function(data) {
AsyncClient.getProfiles();
}.bind(this),
function(err) {
this.setState({ server_error: err.message });
}.bind(this)
);
},
getInitialState: function() {
return {};
},
render: function() {
var server_error = this.state.server_error ? <div className="has-error"><label className='has-error control-label'>{this.state.server_error}</label></div> : null;
var user = this.props.user;
var currentRoles = "Member";
var timestamp = UserStore.getCurrentUser().update_at;
if (user.roles.length > 0) {
currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1);
}
var email = user.email.length > 0 ? user.email : "";
var showMakeMember = user.roles == "admin";
var showMakeAdmin = user.roles == "";
var showMakeActive = false;
var showMakeNotActive = true;
if (user.delete_at > 0) {
currentRoles = "Inactive";
showMakeMember = false;
showMakeAdmin = false;
showMakeActive = true;
showMakeNotActive = false;
}
return ( return (
<div className="row member-div"> <div className='member-list-holder'>
<img className="post-profile-img pull-left" src={"/api/v1/users/" + user.id + "/image?time=" + timestamp} height="36" width="36" /> {memberList}
<span className="member-name">{utils.getDisplayName(user)}</span>
<span className="member-email">{email}</span>
<div className="dropdown member-drop">
<a href="#" className="dropdown-toggle theme" type="button" id="channel_header_dropdown" data-toggle="dropdown" aria-expanded="true">
<span>{currentRoles} </span>
<span className="caret"></span>
</a>
<ul className="dropdown-menu member-menu" role="menu" aria-labelledby="channel_header_dropdown">
{ showMakeAdmin ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleMakeAdmin}>Make Admin</a></li> : "" }
{ showMakeMember ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleMakeMember}>Make Member</a></li> : "" }
{ showMakeActive ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleMakeActive}>Make Active</a></li> : "" }
{ showMakeNotActive ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleMakeNotActive}>Make Inactive</a></li> : "" }
</ul>
</div>
{ server_error }
</div> </div>
); );
} }
}); }
MemberListTeam.propTypes = {
module.exports = React.createClass({ users: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
render: function() { };
return (
<div className="member-list-holder">
{
this.props.users.map(function(user) {
return <MemberListTeamItem key={user.id} user={user} />;
}, this)
}
</div>
);
}
});

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

@@ -0,0 +1,203 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
const UserStore = require('../stores/user_store.jsx');
const Client = require('../utils/client.jsx');
const AsyncClient = require('../utils/async_client.jsx');
const Utils = require('../utils/utils.jsx');
export default class MemberListTeamItem extends React.Component {
constructor(props) {
super(props);
this.handleMakeMember = this.handleMakeMember.bind(this);
this.handleMakeActive = this.handleMakeActive.bind(this);
this.handleMakeNotActive = this.handleMakeNotActive.bind(this);
this.handleMakeAdmin = this.handleMakeAdmin.bind(this);
this.state = {};
}
handleMakeMember() {
const data = {
user_id: this.props.user.id,
new_roles: ''
};
Client.updateRoles(data,
function handleMakeMemberSuccess() {
AsyncClient.getProfiles();
},
function handleMakeMemberError(err) {
this.setState({serverError: err.message});
}.bind(this)
);
}
handleMakeActive() {
Client.updateActive(this.props.user.id, true,
function handleMakeActiveSuccess() {
AsyncClient.getProfiles();
},
function handleMakeActiveError(err) {
this.setState({serverError: err.message});
}.bind(this)
);
}
handleMakeNotActive() {
Client.updateActive(this.props.user.id, false,
function handleMakeNotActiveSuccess() {
AsyncClient.getProfiles();
},
function handleMakeNotActiveError(err) {
this.setState({serverError: err.message});
}.bind(this)
);
}
handleMakeAdmin() {
const data = {
user_id: this.props.user.id,
new_roles: 'admin'
};
Client.updateRoles(data,
function handleMakeAdminSuccess() {
AsyncClient.getProfiles();
},
function handleMakeAdmitError(err) {
this.setState({serverError: err.message});
}.bind(this)
);
}
render() {
let serverError = null;
if (this.state.serverError) {
serverError = (
<div className='has-error'>
<label className='has-error control-label'>{this.state.serverError}</label>
</div>
);
}
const user = this.props.user;
let currentRoles = 'Member';
const timestamp = UserStore.getCurrentUser().update_at;
if (user.roles.length > 0) {
currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1);
}
const email = user.email;
let showMakeMember = user.roles === 'admin';
let showMakeAdmin = user.roles === '';
let showMakeActive = false;
let showMakeNotActive = true;
if (user.delete_at > 0) {
currentRoles = 'Inactive';
showMakeMember = false;
showMakeAdmin = false;
showMakeActive = true;
showMakeNotActive = false;
}
let makeAdmin = null;
if (showMakeAdmin) {
makeAdmin = (
<li role='presentation'>
<a
role='menuitem'
href='#'
onClick={this.handleMakeAdmin}
>
Make Admin
</a>
</li>
);
}
let makeMember = null;
if (showMakeMember) {
makeMember = (
<li role='presentation'>
<a
role='menuitem'
href='#'
onClick={this.handleMakeMember}
>
Make Member
</a>
</li>
);
}
let makeActive = null;
if (showMakeActive) {
makeActive = (
<li role='presentation'>
<a
role='menuitem'
href='#'
onClick={this.handleMakeActive}
>
Make Active
</a>
</li>
);
}
let makeNotActive = null;
if (showMakeNotActive) {
makeNotActive = (
<li role='presentation'>
<a
role='menuitem'
href='#'
onClick={this.handleMakeNotActive}
>
Make Inactive
</a>
</li>
);
}
return (
<div className='row member-div'>
<img
className='post-profile-img pull-left'
src={`/api/v1/users/${user.id}/image?time=${timestamp}`}
height='36'
width='36'
/>
<span className='member-name'>{Utils.getDisplayName(user)}</span>
<span className='member-email'>{email}</span>
<div className='dropdown member-drop'>
<a
href='#'
className='dropdown-toggle theme'
type='button'
id='channel_header_dropdown'
data-toggle='dropdown'
aria-expanded='true'
>
<span>{currentRoles} </span>
<span className='caret'></span>
</a>
<ul
className='dropdown-menu member-menu'
role='menu'
aria-labelledby='channel_header_dropdown'
>
{makeAdmin}
{makeMember}
{makeActive}
{makeNotActive}
</ul>
</div>
{serverError}
</div>
);
}
}
MemberListTeamItem.propTypes = {
user: React.PropTypes.object.isRequired
};

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

@@ -0,0 +1,80 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
export default class PopoverListMembers extends React.Component {
componentDidMount() {
const originalLeave = $.fn.popover.Constructor.prototype.leave;
$.fn.popover.Constructor.prototype.leave = function onLeave(obj) {
let selfObj;
if (obj instanceof this.constructor) {
selfObj = obj;
} else {
selfObj = $(obj.currentTarget)[this.type](this.getDelegateOptions()).data(`bs.${this.type}`);
}
originalLeave.call(this, obj);
if (obj.currentTarget && selfObj.$tip) {
selfObj.$tip.one('mouseenter', function onMouseEnter() {
clearTimeout(selfObj.timeout);
selfObj.$tip.one('mouseleave', function onMouseLeave() {
$.fn.popover.Constructor.prototype.leave.call(selfObj, selfObj);
});
});
}
};
$('#member_popover').popover({placement: 'bottom', trigger: 'click', html: true});
$('body').on('click', function onClick(e) {
if ($(e.target.parentNode.parentNode)[0] !== $('#member_popover')[0] && $(e.target).parents('.popover.in').length === 0) {
$('#member_popover').popover('hide');
}
});
}
render() {
let popoverHtml = '';
const members = this.props.members;
let count;
if (members.length > 20) {
count = '20+';
} else {
count = members.length || '-';
}
if (members) {
members.sort(function compareByLocal(a, b) {
return a.username.localeCompare(b.username);
});
members.forEach(function addMemberElement(m) {
popoverHtml += `<div class='text--nowrap'>${m.username}</div>`;
});
}
return (
<div
id='member_popover'
data-toggle='popover'
data-content={popoverHtml}
data-original-title='Members'
>
<div
id='member_tooltip'
data-placement='left'
data-toggle='tooltip'
title='View Channel Members'
>
{count}
<span
className='glyphicon glyphicon-user'
aria-hidden='true'
/>
</div>
</div>
);
}
}
PopoverListMembers.propTypes = {
members: React.PropTypes.array.isRequired,
channelId: React.PropTypes.string.isRequired
};

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

@@ -1,95 +1,140 @@
// 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 FileAttachmentList = require('./file_attachment_list.jsx'); const FileAttachmentList = require('./file_attachment_list.jsx');
var UserStore = require('../stores/user_store.jsx'); const UserStore = require('../stores/user_store.jsx');
var utils = require('../utils/utils.jsx'); const Utils = require('../utils/utils.jsx');
var Constants = require('../utils/constants.jsx'); const Constants = require('../utils/constants.jsx');
module.exports = React.createClass({ export default class PostBody extends React.Component {
componentWillReceiveProps: function(nextProps) { constructor(props) {
var linkData = utils.extractLinks(nextProps.post.message); super(props);
const linkData = Utils.extractLinks(this.props.post.message);
this.state = {links: linkData.links, message: linkData.text};
}
componentWillReceiveProps(nextProps) {
const linkData = Utils.extractLinks(nextProps.post.message);
this.setState({links: linkData.links, message: linkData.text}); this.setState({links: linkData.links, message: linkData.text});
}, }
getInitialState: function() { render() {
var linkData = utils.extractLinks(this.props.post.message); const post = this.props.post;
return {links: linkData.links, message: linkData.text}; const filenames = this.props.post.filenames;
}, const parentPost = this.props.parentPost;
render: function() { const inner = Utils.textToJsx(this.state.message);
var post = this.props.post;
var filenames = this.props.post.filenames;
var parentPost = this.props.parentPost;
var inner = utils.textToJsx(this.state.message);
var comment = ''; let comment = '';
var reply = ''; let postClass = '';
var postClass = '';
if (parentPost) { if (parentPost) {
var profile = UserStore.getProfile(parentPost.user_id); const profile = UserStore.getProfile(parentPost.user_id);
var apostrophe = '';
var name = '...'; let apostrophe = '';
let name = '...';
if (profile != null) { if (profile != null) {
if (profile.username.slice(-1) === 's') { if (profile.username.slice(-1) === 's') {
apostrophe = '\''; apostrophe = '\'';
} else { } else {
apostrophe = '\'s'; apostrophe = '\'s';
} }
name = <a className='theme' onClick={function searchName() { utils.searchForTerm(profile.username); }}>{profile.username}</a>; name = (
<a
className='theme'
onClick={Utils.searchForTerm.bind(null, profile.username)}
>
{profile.username}
</a>
);
} }
var message = ''; let message = '';
if (parentPost.message) { if (parentPost.message) {
message = utils.replaceHtmlEntities(parentPost.message); message = Utils.replaceHtmlEntities(parentPost.message);
} else if (parentPost.filenames.length) { } else if (parentPost.filenames.length) {
message = parentPost.filenames[0].split('/').pop(); message = parentPost.filenames[0].split('/').pop();
if (parentPost.filenames.length === 2) { if (parentPost.filenames.length === 2) {
message += ' plus 1 other file'; message += ' plus 1 other file';
} else if (parentPost.filenames.length > 2) { } else if (parentPost.filenames.length > 2) {
message += ' plus ' + (parentPost.filenames.length - 1) + ' other files'; message += ` plus ${parentPost.filenames.length - 1} other files`;
} }
} }
comment = ( comment = (
<p className='post-link'> <p className='post-link'>
<span>Commented on {name}{apostrophe} message: <a className='theme' onClick={this.props.handleCommentClick}>{message}</a></span> <span>
Commented on {name}{apostrophe} message:
<a
className='theme'
onClick={this.props.handleCommentClick}
>
{message}
</a>
</span>
</p> </p>
); );
postClass += ' post-comment'; postClass += ' post-comment';
} }
var loading; let loading;
if (post.state === Constants.POST_FAILED) { if (post.state === Constants.POST_FAILED) {
postClass += ' post-fail'; postClass += ' post-fail';
loading = <a className='theme post-retry pull-right' href='#' onClick={this.props.retryPost}>Retry</a>; loading = (
<a
className='theme post-retry pull-right'
href='#'
onClick={this.props.retryPost}
>
Retry
</a>
);
} else if (post.state === Constants.POST_LOADING) { } else if (post.state === Constants.POST_LOADING) {
postClass += ' post-waiting'; postClass += ' post-waiting';
loading = <img className='post-loading-gif pull-right' src='/static/images/load.gif'/>; loading = (
<img
className='post-loading-gif pull-right'
src='/static/images/load.gif'
/>
);
} }
var embed; let embed;
if (filenames.length === 0 && this.state.links) { if (filenames.length === 0 && this.state.links) {
embed = utils.getEmbed(this.state.links[0]); embed = Utils.getEmbed(this.state.links[0]);
} }
var fileAttachmentHolder = ''; let fileAttachmentHolder = '';
if (filenames && filenames.length > 0) { if (filenames && filenames.length > 0) {
fileAttachmentHolder = (<FileAttachmentList fileAttachmentHolder = (
filenames={filenames} <FileAttachmentList
modalId={'view_image_modal_' + post.id} filenames={filenames}
channelId={post.channel_id} modalId={`view_image_modal_${post.id}`}
userId={post.user_id} />); channelId={post.channel_id}
userId={post.user_id}
/>
);
} }
return ( return (
<div className='post-body'> <div className='post-body'>
{comment} {comment}
<p key={post.id + '_message'} className={postClass}>{loading}<span>{inner}</span></p> <p
key={`${post.id}_message`}
className={postClass}
>
{loading}<span>{inner}</span>
</p>
{fileAttachmentHolder} {fileAttachmentHolder}
{embed} {embed}
</div> </div>
); );
} }
}); }
PostBody.propTypes = {
post: React.PropTypes.object.isRequired,
parentPost: React.PropTypes.object,
retryPost: React.PropTypes.func.isRequired,
handleCommentClick: React.PropTypes.func.isRequired
};

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

@@ -1,147 +1,217 @@
// 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.
const Utils = require('../utils/utils.jsx');
const Client = require('../utils/client.jsx');
const AsyncClient = require('../utils/async_client.jsx');
const ChannelStore = require('../stores/channel_store.jsx');
var utils = require('../utils/utils.jsx'); export default class RenameChannelModal extends React.Component {
var Client = require('../utils/client.jsx'); constructor(props) {
var AsyncClient = require('../utils/async_client.jsx'); super(props);
var ChannelStore = require('../stores/channel_store.jsx');
var TeamStore = require('../stores/team_store.jsx');
var Constants = require('../utils/constants.jsx');
module.exports = React.createClass({ this.handleSubmit = this.handleSubmit.bind(this);
handleSubmit: function(e) { this.onNameChange = this.onNameChange.bind(this);
this.onDisplayNameChange = this.onDisplayNameChange.bind(this);
this.displayNameKeyUp = this.displayNameKeyUp.bind(this);
this.handleClose = this.handleClose.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
displayName: '',
channelName: '',
channelId: '',
serverError: '',
nameError: '',
displayNameError: '',
invalid: false
};
}
handleSubmit(e) {
e.preventDefault(); e.preventDefault();
if (this.state.channel_id.length !== 26) return; if (this.state.channelId.length !== 26) {
return;
}
var channel = ChannelStore.get(this.state.channel_id); let channel = ChannelStore.get(this.state.channelId);
var oldName = channel.name const oldName = channel.name;
var oldDisplayName = channel.display_name const oldDisplayName = channel.displayName;
var state = { server_error: "" }; let state = {serverError: ''};
channel.display_name = this.state.display_name.trim(); channel.display_name = this.state.displayName.trim();
if (!channel.display_name) { if (!channel.display_name) {
state.display_name_error = "This field is required"; state.displayNameError = 'This field is required';
state.inValid = true; state.invalid = true;
} } else if (channel.display_name.length > 22) {
else if (channel.display_name.length > 22) { state.displayNameError = 'This field must be less than 22 characters';
state.display_name_error = "This field must be less than 22 characters"; state.invalid = true;
state.inValid = true; } else {
} state.displayNameError = '';
else {
state.display_name_error = "";
} }
channel.name = this.state.channel_name.trim(); channel.name = this.state.channelName.trim();
if (!channel.name) { if (!channel.name) {
state.name_error = "This field is required"; state.nameError = 'This field is required';
state.inValid = true; state.invalid = true;
} } else if (channel.name.length > 22) {
else if(channel.name.length > 22){ state.nameError = 'This field must be less than 22 characters';
state.name_error = "This field must be less than 22 characters"; state.invalid = true;
state.inValid = true; } else {
} let cleanedName = Utils.cleanUpUrlable(channel.name);
else { if (cleanedName !== channel.name) {
var cleaned_name = utils.cleanUpUrlable(channel.name); state.nameError = 'Must be lowercase alphanumeric characters';
if (cleaned_name != channel.name) { state.invalid = true;
state.name_error = "Must be lowercase alphanumeric characters"; } else {
state.inValid = true; state.nameError = '';
}
else {
state.name_error = "";
} }
} }
this.setState(state); this.setState(state);
if (state.inValid) if (state.invalid || (oldName === channel.name && oldDisplayName === channel.display_name)) {
return;
if (oldName == channel.name && oldDisplayName == channel.display_name)
return; return;
}
Client.updateChannel(channel, Client.updateChannel(channel,
function(data, text, req) { function handleUpdateSuccess() {
$(this.refs.modal.getDOMNode()).modal('hide'); $(React.findDOMNode(this.refs.modal)).modal('hide');
AsyncClient.getChannel(channel.id); AsyncClient.getChannel(channel.id);
utils.updateTabTitle(channel.display_name); Utils.updateTabTitle(channel.display_name);
utils.updateAddressBar(channel.name); Utils.updateAddressBar(channel.name);
this.refs.display_name.getDOMNode().value = ""; React.findDOMNode(this.refs.displayName).value = '';
this.refs.channel_name.getDOMNode().value = ""; React.findDOMNode(this.refs.channelName).value = '';
}.bind(this), }.bind(this),
function(err) { function handleUpdateError(err) {
state.server_error = err.message; state.serverError = err.message;
state.inValid = true; state.invalid = true;
this.setState(state); this.setState(state);
}.bind(this) }.bind(this)
); );
}, }
onNameChange: function() { onNameChange() {
this.setState({ channel_name: this.refs.channel_name.getDOMNode().value }) this.setState({channelName: React.findDOMNode(this.refs.channelName).value});
}, }
onDisplayNameChange: function() { onDisplayNameChange() {
this.setState({ display_name: this.refs.display_name.getDOMNode().value }) this.setState({displayName: React.findDOMNode(this.refs.displayName).value});
}, }
displayNameKeyUp: function(e) { displayNameKeyUp() {
var display_name = this.refs.display_name.getDOMNode().value.trim(); const displayName = React.findDOMNode(this.refs.displayName).value.trim();
var channel_name = utils.cleanUpUrlable(display_name); const channelName = Utils.cleanUpUrlable(displayName);
this.refs.channel_name.getDOMNode().value = channel_name; React.findDOMNode(this.refs.channelName).value = channelName;
this.setState({ channel_name: channel_name }) this.setState({channelName: channelName});
}, }
handleClose: function() { handleClose() {
this.setState({display_name: "", channel_name: "", display_name_error: "", server_error: "", name_error: ""}); this.state = {
}, displayName: '',
componentDidMount: function() { channelName: '',
var self = this; channelId: '',
$(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { serverError: '',
var button = $(e.relatedTarget); nameError: '',
self.setState({ display_name: button.attr('data-display'), channel_name: button.attr('data-name'), channel_id: button.attr('data-channelid') }); displayNameError: '',
}); invalid: false
$(this.refs.modal.getDOMNode()).on('hidden.bs.modal', this.handleClose); };
}, }
componentWillUnmount: function() { componentDidMount() {
$(this.refs.modal.getDOMNode()).off('hidden.bs.modal', this.handleClose); $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) {
}, const button = $(e.relatedTarget);
getInitialState: function() { this.setState({displayName: button.attr('data-display'), channelName: button.attr('data-name'), channelId: button.attr('data-channelid')});
return { display_name: "", channel_name: "", channel_id: "" }; }.bind(this));
}, $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose);
render: function() { }
componentWillUnmount() {
$(React.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose);
}
render() {
let displayNameError = null;
let displayNameClass = 'form-group';
if (this.state.displayNameError) {
displayNameError = <label className='control-label'>{this.state.displayNameError}</label>;
displayNameClass += ' has-error';
}
var display_name_error = this.state.display_name_error ? <label className='control-label'>{ this.state.display_name_error }</label> : null; let nameError = null;
var name_error = this.state.name_error ? <label className='control-label'>{ this.state.name_error }</label> : null; let nameClass = 'form-group';
var server_error = this.state.server_error ? <div className='form-group has-error'><label className='control-label'>{ this.state.server_error }</label></div> : null; if (this.state.nameError) {
nameError = <label className='control-label'>{this.state.nameError}</label>;
nameClass += ' has-error';
}
let serverError = null;
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
return ( return (
<div className="modal fade" ref="modal" id="rename_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='rename_channel'
<button type="button" className="close" data-dismiss="modal"> tabIndex='-1'
<span aria-hidden="true">&times;</span> role='dialog'
<span className="sr-only">Close</span> aria-hidden='true'
>
<div className='modal-dialog'>
<div className='modal-content'>
<div className='modal-header'>
<button
type='button'
className='close'
data-dismiss='modal'
>
<span aria-hidden='true'>&times;</span>
<span className='sr-only'>Close</span>
</button> </button>
<h4 className="modal-title">Rename Channel</h4> <h4 className='modal-title'>Rename Channel</h4>
</div> </div>
<form role="form"> <form role='form'>
<div className="modal-body"> <div className='modal-body'>
<div className={ this.state.display_name_error ? "form-group has-error" : "form-group" }> <div className={displayNameClass}>
<label className='control-label'>Display Name</label> <label className='control-label'>Display Name</label>
<input onKeyUp={this.displayNameKeyUp} onChange={this.onDisplayNameChange} type="text" ref="display_name" className="form-control" placeholder="Enter display name" value={this.state.display_name} maxLength="64" /> <input
{ display_name_error } onKeyUp={this.displayNameKeyUp}
onChange={this.onDisplayNameChange}
type='text'
ref='displayName'
className='form-control'
placeholder='Enter display name'
value={this.state.displayName}
maxLength='64'
/>
{displayNameError}
</div> </div>
<div className={ this.state.name_error ? "form-group has-error" : "form-group" }> <div className={nameClass}>
<label className='control-label'>Handle</label> <label className='control-label'>Handle</label>
<input onChange={this.onNameChange} type="text" className="form-control" ref="channel_name" placeholder="lowercase alphanumeric's only" value={this.state.channel_name} maxLength="64" /> <input
{ name_error } onChange={this.onNameChange}
type='text'
className='form-control'
ref='channelName'
placeholder='lowercase alphanumeric&#39;s only'
value={this.state.channelName}
maxLength='64'
/>
{nameError}
</div> </div>
{ server_error } {serverError}
</div> </div>
<div className="modal-footer"> <div className='modal-footer'>
<button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button> <button
<button onClick={this.handleSubmit} type="submit" className="btn btn-primary">Save</button> type='button'
className='btn btn-default'
data-dismiss='modal'
>
Cancel
</button>
<button
onClick={this.handleSubmit}
type='submit'
className='btn btn-primary'
>
Save
</button>
</div> </div>
</form> </form>
</div> </div>
@@ -149,4 +219,4 @@ module.exports = React.createClass({
</div> </div>
); );
} }
}); }

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

@@ -1,9 +1,9 @@
// 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 AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Constants = require('../utils/constants.jsx'); const Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes; const ActionTypes = Constants.ActionTypes;
export default class RhsHeaderPost extends React.Component { export default class RhsHeaderPost extends React.Component {
constructor(props) { constructor(props) {
@@ -43,7 +43,7 @@ export default class RhsHeaderPost extends React.Component {
}); });
} }
render() { render() {
var back; let back;
if (this.props.fromSearch) { if (this.props.fromSearch) {
back = ( back = (
<a <a

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

@@ -1,19 +1,23 @@
// 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 SettingItemMin extends React.Component {
displayName: 'SettingsItemMin', render() {
propTypes: { let editButton = null;
title: React.PropTypes.string,
disableOpen: React.PropTypes.bool,
updateSection: React.PropTypes.func,
describe: React.PropTypes.string
},
render: function() {
var editButton = '';
if (!this.props.disableOpen) { if (!this.props.disableOpen) {
editButton = <li className='col-sm-2 section-edit'><a className='section-edit theme' href='#' onClick={this.props.updateSection}>Edit</a></li>; editButton = (
<li className='col-sm-2 section-edit'>
<a
className='section-edit theme'
href='#'
onClick={this.props.updateSection}
>
Edit
</a>
</li>
);
} }
return ( return (
<ul className='section-min'> <ul className='section-min'>
<li className='col-sm-10 section-title'>{this.props.title}</li> <li className='col-sm-10 section-title'>{this.props.title}</li>
@@ -22,4 +26,11 @@ module.exports = React.createClass({
</ul> </ul>
); );
} }
}); }
SettingItemMin.propTypes = {
title: React.PropTypes.string,
disableOpen: React.PropTypes.bool,
updateSection: React.PropTypes.func,
describe: React.PropTypes.string
};

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

@@ -1,24 +1,56 @@
// 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'); export default class SettingsSidebar extends React.Component {
constructor(props) {
super(props);
module.exports = React.createClass({ this.handleClick = this.handleClick.bind(this);
displayName:'SettingsSidebar', }
updateTab: function(tab) { handleClick(tab) {
this.props.updateTab(tab); this.props.updateTab(tab.name);
$('.settings-modal').addClass('display--content'); $('.settings-modal').addClass('display--content');
}, }
render: function() { render() {
var self = this; let tabList = this.props.tabs.map(function makeTab(tab) {
let key = `${tab.name}_li`;
let className = '';
if (this.props.activeTab === tab.name) {
className = 'active';
}
return (
<li
key={key}
className={className}
>
<a
href='#'
onClick={this.handleClick.bind(null, tab)}
>
<i className={tab.icon} />
{tab.uiName}
</a>
</li>
);
}.bind(this));
return ( return (
<div className=""> <div>
<ul className="nav nav-pills nav-stacked"> <ul className='nav nav-pills nav-stacked'>
{this.props.tabs.map(function(tab) { {tabList}
return <li key={tab.name+'_li'} className={self.props.activeTab == tab.name ? 'active' : ''}><a key={tab.name + '_a'} href="#" onClick={function(){self.updateTab(tab.name);}}><i key={tab.name+'_i'} className={tab.icon}></i>{tab.uiName}</a></li>
})}
</ul> </ul>
</div> </div>
); );
} }
}); }
SettingsSidebar.propTypes = {
tabs: React.PropTypes.arrayOf(React.PropTypes.shape({
name: React.PropTypes.string.isRequired,
uiName: React.PropTypes.string.isRequired,
icon: React.PropTypes.string.isRequired
})).isRequired,
activeTab: React.PropTypes.string,
updateTab: React.PropTypes.func.isRequired
};

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

@@ -1,10 +1,10 @@
// 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 ChoosePage = require('./team_signup_choose_auth.jsx'); const ChoosePage = require('./team_signup_choose_auth.jsx');
var EmailSignUpPage = require('./team_signup_with_email.jsx'); const EmailSignUpPage = require('./team_signup_with_email.jsx');
var SSOSignupPage = require('./team_signup_with_sso.jsx'); const SSOSignupPage = require('./team_signup_with_sso.jsx');
var Constants = require('../utils/constants.jsx'); const Constants = require('../utils/constants.jsx');
export default class TeamSignUp extends React.Component { export default class TeamSignUp extends React.Component {
constructor(props) { constructor(props) {
@@ -30,14 +30,14 @@ export default class TeamSignUp extends React.Component {
return <EmailSignUpPage />; return <EmailSignUpPage />;
} else if (this.state.page === 'service' && this.state.service !== '') { } else if (this.state.page === 'service' && this.state.service !== '') {
return <SSOSignupPage service={this.state.service} />; return <SSOSignupPage service={this.state.service} />;
} else {
return (
<ChoosePage
services={this.props.services}
updatePage={this.updatePage}
/>
);
} }
return (
<ChoosePage
services={this.props.services}
updatePage={this.updatePage}
/>
);
} }
} }

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

@@ -1,11 +1,11 @@
// 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 SettingItemMin = require('./setting_item_min.jsx'); const SettingItemMin = require('./setting_item_min.jsx');
var SettingItemMax = require('./setting_item_max.jsx'); const SettingItemMax = require('./setting_item_max.jsx');
var client = require('../utils/client.jsx'); const Client = require('../utils/client.jsx');
var utils = require('../utils/utils.jsx'); const Utils = require('../utils/utils.jsx');
export default class GeneralTab extends React.Component { export default class GeneralTab extends React.Component {
constructor(props) { constructor(props) {
@@ -21,10 +21,10 @@ export default class GeneralTab extends React.Component {
handleNameSubmit(e) { handleNameSubmit(e) {
e.preventDefault(); e.preventDefault();
var state = {serverError: '', clientError: ''}; let state = {serverError: '', clientError: ''};
var valid = true; let valid = true;
var name = this.state.name.trim(); const name = this.state.name.trim();
if (!name) { if (!name) {
state.clientError = 'This field is required'; state.clientError = 'This field is required';
valid = false; valid = false;
@@ -41,10 +41,10 @@ export default class GeneralTab extends React.Component {
return; return;
} }
var data = {}; let data = {};
data.new_name = name; data.new_name = name;
client.updateTeamDisplayName(data, Client.updateTeamDisplayName(data,
function nameChangeSuccess() { function nameChangeSuccess() {
this.props.updateSection(''); this.props.updateSection('');
$('#team_settings').modal('hide'); $('#team_settings').modal('hide');
@@ -84,8 +84,8 @@ export default class GeneralTab extends React.Component {
this.setState({name: e.target.value}); this.setState({name: e.target.value});
} }
render() { render() {
var clientError = null; let clientError = null;
var serverError = null; let serverError = null;
if (this.state.clientError) { if (this.state.clientError) {
clientError = this.state.clientError; clientError = this.state.clientError;
} }
@@ -93,18 +93,21 @@ export default class GeneralTab extends React.Component {
serverError = this.state.serverError; serverError = this.state.serverError;
} }
var nameSection; let nameSection;
if (this.props.activeSection === 'name') { if (this.props.activeSection === 'name') {
let inputs = []; let inputs = [];
let teamNameLabel = utils.toTitleCase(strings.Team) + ' Name'; let teamNameLabel = Utils.toTitleCase(strings.Team) + ' Name';
if (utils.isMobile()) { if (Utils.isMobile()) {
teamNameLabel = ''; teamNameLabel = '';
} }
inputs.push( inputs.push(
<div key='teamNameSetting' className='form-group'> <div
key='teamNameSetting'
className='form-group'
>
<label className='col-sm-5 control-label'>{teamNameLabel}</label> <label className='col-sm-5 control-label'>{teamNameLabel}</label>
<div className='col-sm-7'> <div className='col-sm-7'>
<input <input
@@ -119,7 +122,7 @@ export default class GeneralTab extends React.Component {
nameSection = ( nameSection = (
<SettingItemMax <SettingItemMax
title={utils.toTitleCase(strings.Team) + ' Name'} title={`${Utils.toTitleCase(strings.Team)} Name`}
inputs={inputs} inputs={inputs}
submit={this.handleNameSubmit} submit={this.handleNameSubmit}
server_error={serverError} server_error={serverError}
@@ -132,7 +135,7 @@ export default class GeneralTab extends React.Component {
nameSection = ( nameSection = (
<SettingItemMin <SettingItemMin
title={utils.toTitleCase(strings.Team) + ' Name'} title={`${Utils.toTitleCase(strings.Team)} Name`}
describe={describe} describe={describe}
updateSection={this.onUpdateSection} updateSection={this.onUpdateSection}
/> />

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

@@ -1,70 +1,96 @@
// 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 SettingsSidebar = require('./settings_sidebar.jsx'); const SettingsSidebar = require('./settings_sidebar.jsx');
var TeamSettings = require('./team_settings.jsx'); const TeamSettings = require('./team_settings.jsx');
module.exports = React.createClass({ export default class TeamSettingsModal extends React.Component {
displayName: 'Team Settings Modal', constructor(props) {
propTypes: { super(props);
teamDisplayName: React.PropTypes.string.isRequired
}, this.updateTab = this.updateTab.bind(this);
componentDidMount: function() { this.updateSection = this.updateSection.bind(this);
$('body').on('click', '.modal-back', function onClick() {
this.state = {
activeTab: 'general',
activeSection: ''
};
}
componentDidMount() {
$('body').on('click', '.modal-back', function handleBackClick() {
$(this).closest('.modal-dialog').removeClass('display--content'); $(this).closest('.modal-dialog').removeClass('display--content');
}); });
$('body').on('click', '.modal-header .close', function onClick() { $('body').on('click', '.modal-header .close', function handleCloseClick() {
setTimeout(function removeContent() { setTimeout(function removeContent() {
$('.modal-dialog.display--content').removeClass('display--content'); $('.modal-dialog.display--content').removeClass('display--content');
}, 500); }, 500);
}); });
}, }
updateTab: function(tab) { updateTab(tab) {
this.setState({activeTab: tab, activeSection: ''}); this.setState({activeTab: tab, activeSection: ''});
}, }
updateSection: function(section) { updateSection(section) {
this.setState({activeSection: section}); this.setState({activeSection: section});
}, }
getInitialState: function() { render() {
return {activeTab: 'general', activeSection: ''}; let tabs = [];
},
render: function() {
var tabs = [];
tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'}); tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'});
tabs.push({name: 'import', uiName: 'Import', icon: 'glyphicon glyphicon-upload'}); tabs.push({name: 'import', uiName: 'Import', icon: 'glyphicon glyphicon-upload'});
tabs.push({name: 'feature', uiName: 'Advanced', icon: 'glyphicon glyphicon-wrench'}); tabs.push({name: 'feature', uiName: 'Advanced', icon: 'glyphicon glyphicon-wrench'});
return ( return (
<div className='modal fade' ref='modal' id='team_settings' role='dialog' tabIndex='-1' aria-hidden='true'> <div
<div className='modal-dialog settings-modal'> className='modal fade'
<div className='modal-content'> ref='modal'
<div className='modal-header'> id='team_settings'
<button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button> role='dialog'
<h4 className='modal-title' ref='title'>Team Settings</h4> tabIndex='-1'
</div> aria-hidden='true'
<div className='modal-body'> >
<div className='settings-table'> <div className='modal-dialog settings-modal'>
<div className='settings-links'> <div className='modal-content'>
<SettingsSidebar <div className='modal-header'>
tabs={tabs} <button
activeTab={this.state.activeTab} type='button'
updateTab={this.updateTab} className='close'
/> data-dismiss='modal'
aria-label='Close'
>
<span aria-hidden='true'>&times;</span>
</button>
<h4
className='modal-title'
ref='title'
>
Team Settings
</h4>
</div> </div>
<div className='settings-content minimize-settings'> <div className='modal-body'>
<TeamSettings <div className='settings-table'>
activeTab={this.state.activeTab} <div className='settings-links'>
activeSection={this.state.activeSection} <SettingsSidebar
updateSection={this.updateSection} tabs={tabs}
teamDisplayName={this.props.teamDisplayName} activeTab={this.state.activeTab}
/> updateTab={this.updateTab}
/>
</div>
<div className='settings-content minimize-settings'>
<TeamSettings
activeTab={this.state.activeTab}
activeSection={this.state.activeSection}
updateSection={this.updateSection}
teamDisplayName={this.props.teamDisplayName}
/>
</div>
</div>
</div> </div>
</div> </div>
</div>
</div> </div>
</div>
</div> </div>
); );
} }
}); }
TeamSettingsModal.propTypes = {
teamDisplayName: React.PropTypes.string.isRequired
};

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

@@ -1,28 +1,28 @@
// 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'); const Utils = require('../utils/utils.jsx');
module.exports = React.createClass({ export default class TeamSignupEmailItem extends React.Component {
displayName: 'TeamSignupEmailItem', constructor(props) {
propTypes: { super(props);
focus: React.PropTypes.bool,
email: React.PropTypes.string this.getValue = this.getValue.bind(this);
}, this.validate = this.validate.bind(this);
getInitialState: function() {
return {}; this.state = {};
}, }
getValue: function() { getValue() {
return this.refs.email.getDOMNode().value.trim(); return React.findDOMNode(this.refs.email).value.trim();
}, }
validate: function(teamEmail) { validate(teamEmail) {
var email = this.refs.email.getDOMNode().value.trim().toLowerCase(); const email = React.findDOMNode(this.refs.email).value.trim().toLowerCase();
if (!email) { if (!email) {
return true; return true;
} }
if (!utils.isEmail(email)) { if (!Utils.isEmail(email)) {
this.state.emailError = 'Please enter a valid email address'; this.state.emailError = 'Please enter a valid email address';
this.setState(this.state); this.setState(this.state);
return false; return false;
@@ -31,13 +31,14 @@ module.exports = React.createClass({
this.setState(this.state); this.setState(this.state);
return false; return false;
} }
this.state.emailError = ''; this.state.emailError = '';
this.setState(this.state); this.setState(this.state);
return true; return true;
}, }
render: function() { render() {
var emailError = null; let emailError = null;
var emailDivClass = 'form-group'; let emailDivClass = 'form-group';
if (this.state.emailError) { if (this.state.emailError) {
emailError = <label className='control-label'>{this.state.emailError}</label>; emailError = <label className='control-label'>{this.state.emailError}</label>;
emailDivClass += ' has-error'; emailDivClass += ' has-error';
@@ -45,9 +46,22 @@ module.exports = React.createClass({
return ( return (
<div className={emailDivClass}> <div className={emailDivClass}>
<input autoFocus={this.props.focus} type='email' ref='email' className='form-control' placeholder='Email Address' defaultValue={this.props.email} maxLength='128' /> <input
autoFocus={this.props.focus}
type='email'
ref='email'
className='form-control'
placeholder='Email Address'
defaultValue={this.props.email}
maxLength='128'
/>
{emailError} {emailError}
</div> </div>
); );
} }
}); }
TeamSignupEmailItem.propTypes = {
focus: React.PropTypes.bool,
email: React.PropTypes.string
};

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

@@ -1,33 +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.
var utils = require('../utils/utils.jsx'); const Utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx'); const Client = require('../utils/client.jsx');
var constants = require('../utils/constants.jsx'); const Constants = require('../utils/constants.jsx');
module.exports = React.createClass({ export default class TeamSignupUrlPage extends React.Component {
displayName: 'TeamSignupURLPage', constructor(props) {
propTypes: { super(props);
state: React.PropTypes.object,
updateParent: React.PropTypes.func this.submitBack = this.submitBack.bind(this);
}, this.submitNext = this.submitNext.bind(this);
submitBack: function(e) { this.handleFocus = this.handleFocus.bind(this);
this.state = {nameError: ''};
}
submitBack(e) {
e.preventDefault(); e.preventDefault();
this.props.state.wizard = 'team_display_name'; this.props.state.wizard = 'team_display_name';
this.props.updateParent(this.props.state); this.props.updateParent(this.props.state);
}, }
submitNext: function(e) { submitNext(e) {
e.preventDefault(); e.preventDefault();
var name = this.refs.name.getDOMNode().value.trim(); const name = React.findDOMNode(this.refs.name).value.trim();
if (!name) { if (!name) {
this.setState({nameError: 'This field is required'}); this.setState({nameError: 'This field is required'});
return; return;
} }
var cleanedName = utils.cleanUpUrlable(name); const cleanedName = Utils.cleanUpUrlable(name);
var urlRegex = /^[a-z]+([a-z\-0-9]+|(__)?)[a-z0-9]+$/g; const urlRegex = /^[a-z]+([a-z\-0-9]+|(__)?)[a-z0-9]+$/g;
if (cleanedName !== name || !urlRegex.test(name)) { if (cleanedName !== name || !urlRegex.test(name)) {
this.setState({nameError: "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash."}); this.setState({nameError: "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash."});
return; return;
@@ -36,14 +40,14 @@ module.exports = React.createClass({
return; return;
} }
for (var index = 0; index < constants.RESERVED_TEAM_NAMES.length; index++) { for (let index = 0; index < Constants.RESERVED_TEAM_NAMES.length; index++) {
if (cleanedName.indexOf(constants.RESERVED_TEAM_NAMES[index]) === 0) { if (cleanedName.indexOf(Constants.RESERVED_TEAM_NAMES[index]) === 0) {
this.setState({nameError: 'This team name is unavailable'}); this.setState({nameError: 'This team name is unavailable'});
return; return;
} }
} }
client.findTeamByName(name, Client.findTeamByName(name,
function success(data) { function success(data) {
if (!data) { if (!data) {
if (config.AllowSignupDomainsWizard) { if (config.AllowSignupDomainsWizard) {
@@ -65,55 +69,88 @@ module.exports = React.createClass({
this.setState(this.state); this.setState(this.state);
}.bind(this) }.bind(this)
); );
}, }
getInitialState: function() { handleFocus(e) {
return {};
},
handleFocus: function(e) {
e.preventDefault(); e.preventDefault();
e.currentTarget.select(); e.currentTarget.select();
}, }
render: function() { render() {
$('body').tooltip({selector: '[data-toggle=tooltip]', trigger: 'hover click'}); $('body').tooltip({selector: '[data-toggle=tooltip]', trigger: 'hover click'});
client.track('signup', 'signup_team_03_url'); Client.track('signup', 'signup_team_03_url');
var nameError = null; let nameError = null;
var nameDivClass = 'form-group'; let nameDivClass = 'form-group';
if (this.state.nameError) { if (this.state.nameError) {
nameError = <label className='control-label'>{this.state.nameError}</label>; nameError = <label className='control-label'>{this.state.nameError}</label>;
nameDivClass += ' has-error'; nameDivClass += ' has-error';
} }
const title = `${Utils.getWindowLocationOrigin()}/`;
return ( return (
<div> <div>
<form> <form>
<img className='signup-team-logo' src='/static/images/logo.png' /> <img
<h2>{utils.toTitleCase(strings.Team) + ' URL'}</h2> className='signup-team-logo'
src='/static/images/logo.png'
/>
<h2>{`${Utils.toTitleCase(strings.Team)} URL`}</h2>
<div className={nameDivClass}> <div className={nameDivClass}>
<div className='row'> <div className='row'>
<div className='col-sm-11'> <div className='col-sm-11'>
<div className='input-group input-group--limit'> <div className='input-group input-group--limit'>
<span data-toggle='tooltip' title={utils.getWindowLocationOrigin() + '/'} className='input-group-addon'>{utils.getWindowLocationOrigin() + '/'}</span> <span
<input type='text' ref='name' className='form-control' placeholder='' maxLength='128' defaultValue={this.props.state.team.name} autoFocus={true} onFocus={this.handleFocus}/> data-toggle='tooltip'
title={title}
className='input-group-addon'
>
{title}
</span>
<input
type='text'
ref='name'
className='form-control'
placeholder=''
maxLength='128'
defaultValue={this.props.state.team.name}
autoFocus={true}
onFocus={this.handleFocus}
/>
</div> </div>
</div> </div>
</div> </div>
{nameError} {nameError}
</div> </div>
<p>{'Choose the web address of your new ' + strings.Team + ':'}</p> <p>{`Choose the web address of your new ${strings.Team}:`}</p>
<ul className='color--light'> <ul className='color--light'>
<li>Short and memorable is best</li> <li>Short and memorable is best</li>
<li>Use lowercase letters, numbers and dashes</li> <li>Use lowercase letters, numbers and dashes</li>
<li>Must start with a letter and can't end in a dash</li> <li>Must start with a letter and can't end in a dash</li>
</ul> </ul>
<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'> <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> </div>
</form> </form>
</div> </div>
); );
} }
}); }
TeamSignupUrlPage.propTypes = {
state: React.PropTypes.object,
updateParent: React.PropTypes.func
};

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

@@ -1,8 +1,8 @@
// 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'); const Utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx'); const Client = require('../utils/client.jsx');
export default class EmailSignUpPage extends React.Component { export default class EmailSignUpPage extends React.Component {
constructor() { constructor() {
@@ -14,11 +14,11 @@ export default class EmailSignUpPage extends React.Component {
} }
handleSubmit(e) { handleSubmit(e) {
e.preventDefault(); e.preventDefault();
var team = {}; let team = {};
var state = {serverError: ''}; let state = {serverError: ''};
team.email = this.refs.email.getDOMNode().value.trim().toLowerCase(); team.email = React.findDOMNode(this.refs.email).value.trim().toLowerCase();
if (!team.email || !utils.isEmail(team.email)) { if (!team.email || !Utils.isEmail(team.email)) {
state.emailError = 'Please enter a valid email address'; state.emailError = 'Please enter a valid email address';
state.inValid = true; state.inValid = true;
} else { } else {
@@ -30,12 +30,12 @@ export default class EmailSignUpPage extends React.Component {
return; return;
} }
client.signupTeam(team.email, Client.signupTeam(team.email,
function success(data) { function success(data) {
if (data.follow_link) { if (data.follow_link) {
window.location.href = data.follow_link; window.location.href = data.follow_link;
} else { } else {
window.location.href = '/signup_team_confirm/?email=' + encodeURIComponent(team.email); window.location.href = `/signup_team_confirm/?email=${encodeURIComponent(team.email)}`;
} }
}, },
function fail(err) { function fail(err) {
@@ -69,7 +69,7 @@ export default class EmailSignUpPage extends React.Component {
</button> </button>
</div> </div>
<div className='form-group margin--extra-2x'> <div className='form-group margin--extra-2x'>
<span><a href='/find_team'>{'Find my ' + strings.Team}</a></span> <span><a href='/find_team'>{`Find my ${strings.Team}`}</a></span>
</div> </div>
</form> </form>
); );

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

@@ -1,66 +1,93 @@
// 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 AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var PostStore = require('../stores/post_store.jsx'); const PostStore = require('../stores/post_store.jsx');
var CommandList = require('./command_list.jsx'); const CommandList = require('./command_list.jsx');
var ErrorStore = require('../stores/error_store.jsx'); const ErrorStore = require('../stores/error_store.jsx');
var AsyncClient = require('../utils/async_client.jsx'); const AsyncClient = require('../utils/async_client.jsx');
var utils = require('../utils/utils.jsx'); const Utils = require('../utils/utils.jsx');
var Constants = require('../utils/constants.jsx'); const Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes; const ActionTypes = Constants.ActionTypes;
function getStateFromStores() { export default class Textbox extends React.Component {
var error = ErrorStore.getLastError(); constructor(props) {
super(props);
if (error) { this.getStateFromStores = this.getStateFromStores.bind(this);
return {message: error.message}; this.onListenerChange = this.onListenerChange.bind(this);
this.onRecievedError = this.onRecievedError.bind(this);
this.onTimerInterrupt = this.onTimerInterrupt.bind(this);
this.updateMentionTab = this.updateMentionTab.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleBackspace = this.handleBackspace.bind(this);
this.checkForNewMention = this.checkForNewMention.bind(this);
this.addMention = this.addMention.bind(this);
this.addCommand = this.addCommand.bind(this);
this.resize = this.resize.bind(this);
this.handleFocus = this.handleFocus.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.handlePaste = this.handlePaste.bind(this);
this.state = {
mentionText: '-1',
mentions: [],
connection: '',
timerInterrupt: null
};
this.caret = -1;
this.addedMention = false;
this.doProcessMentions = false;
this.mentions = [];
} }
return {message: null}; getStateFromStores() {
} const error = ErrorStore.getLastError();
module.exports = React.createClass({ if (error) {
displayName: 'Textbox', return {message: error.message};
caret: -1, }
addedMention: false,
doProcessMentions: false, return {message: null};
mentions: [], }
componentDidMount: function() { componentDidMount() {
PostStore.addAddMentionListener(this.onListenerChange); PostStore.addAddMentionListener(this.onListenerChange);
ErrorStore.addChangeListener(this.onRecievedError); ErrorStore.addChangeListener(this.onRecievedError);
this.resize(); this.resize();
this.updateMentionTab(null); this.updateMentionTab(null);
}, }
componentWillUnmount: function() { componentWillUnmount() {
PostStore.removeAddMentionListener(this.onListenerChange); PostStore.removeAddMentionListener(this.onListenerChange);
ErrorStore.removeChangeListener(this.onRecievedError); ErrorStore.removeChangeListener(this.onRecievedError);
}, }
onListenerChange: function(id, username) { onListenerChange(id, username) {
if (id === this.props.id) { if (id === this.props.id) {
this.addMention(username); this.addMention(username);
} }
}, }
onRecievedError: function() { onRecievedError() {
var errorState = getStateFromStores(); const errorState = this.getStateFromStores();
if (this.state.timerInterrupt != null) { if (this.state.timerInterrupt !== null) {
window.clearInterval(this.state.timerInterrupt); window.clearInterval(this.state.timerInterrupt);
this.setState({timerInterrupt: null}); this.setState({timerInterrupt: null});
} }
if (errorState.message === 'There appears to be a problem with your internet connection') { if (errorState.message === 'There appears to be a problem with your internet connection') {
this.setState({connection: 'bad-connection'}); this.setState({connection: 'bad-connection'});
var timerInterrupt = window.setInterval(this.onTimerInterrupt, 5000); const timerInterrupt = window.setInterval(this.onTimerInterrupt, 5000);
this.setState({timerInterrupt: timerInterrupt}); this.setState({timerInterrupt: timerInterrupt});
} else { } else {
this.setState({connection: ''}); this.setState({connection: ''});
} }
}, }
onTimerInterrupt: function() { onTimerInterrupt() {
//Since these should only happen when you have no connection and slightly briefly after any // Since these should only happen when you have no connection and slightly briefly after any
//performance hit should not matter // performance hit should not matter
if (this.state.connection === 'bad-connection') { if (this.state.connection === 'bad-connection') {
AppDispatcher.handleServerAction({ AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_ERROR, type: ActionTypes.RECIEVED_ERROR,
@@ -72,10 +99,10 @@ module.exports = React.createClass({
window.clearInterval(this.state.timerInterrupt); window.clearInterval(this.state.timerInterrupt);
this.setState({timerInterrupt: null}); this.setState({timerInterrupt: null});
}, }
componentDidUpdate: function() { componentDidUpdate() {
if (this.caret >= 0) { if (this.caret >= 0) {
utils.setCaretPosition(this.refs.message.getDOMNode(), this.caret); Utils.setCaretPosition(React.findDOMNode(this.refs.message), this.caret);
this.caret = -1; this.caret = -1;
} }
if (this.doProcessMentions) { if (this.doProcessMentions) {
@@ -83,40 +110,35 @@ module.exports = React.createClass({
this.doProcessMentions = false; this.doProcessMentions = false;
} }
this.resize(); this.resize();
}, }
componentWillReceiveProps: function(nextProps) { componentWillReceiveProps(nextProps) {
if (!this.addedMention) { if (!this.addedMention) {
this.checkForNewMention(nextProps.messageText); this.checkForNewMention(nextProps.messageText);
} }
var text = this.refs.message.getDOMNode().value; const text = React.findDOMNode(this.refs.message).value;
if (nextProps.channelId !== this.props.channelId || nextProps.messageText !== text) { if (nextProps.channelId !== this.props.channelId || nextProps.messageText !== text) {
this.doProcessMentions = true; this.doProcessMentions = true;
} }
this.addedMention = false; this.addedMention = false;
this.refs.commands.getSuggestedCommands(nextProps.messageText); this.refs.commands.getSuggestedCommands(nextProps.messageText);
this.resize(); this.resize();
}, }
getInitialState: function() { updateMentionTab(mentionText) {
return {mentionText: '-1', mentions: [], connection: '', timerInterrupt: null};
},
updateMentionTab: function(mentionText) {
var self = this;
// using setTimeout so dispatch isn't called during an in progress dispatch // using setTimeout so dispatch isn't called during an in progress dispatch
setTimeout(function() { setTimeout(function updateMentionTabAfterTimeout() {
AppDispatcher.handleViewAction({ AppDispatcher.handleViewAction({
type: ActionTypes.RECIEVED_MENTION_DATA, type: ActionTypes.RECIEVED_MENTION_DATA,
id: self.props.id, id: this.props.id,
mention_text: mentionText mention_text: mentionText
}); });
}, 1); }.bind(this), 1);
}, }
handleChange: function() { handleChange() {
this.props.onUserInput(this.refs.message.getDOMNode().value); this.props.onUserInput(React.findDOMNode(this.refs.message).value);
this.resize(); this.resize();
}, }
handleKeyPress: function(e) { handleKeyPress(e) {
var text = this.refs.message.getDOMNode().value; const text = React.findDOMNode(this.refs.message).value;
if (!this.refs.commands.isEmpty() && text.indexOf('/') === 0 && e.which === 13) { if (!this.refs.commands.isEmpty() && text.indexOf('/') === 0 && e.which === 13) {
this.refs.commands.addFirstCommand(); this.refs.commands.addFirstCommand();
@@ -125,10 +147,10 @@ module.exports = React.createClass({
} }
if (!this.doProcessMentions) { if (!this.doProcessMentions) {
var caret = utils.getCaretPosition(this.refs.message.getDOMNode()); const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message));
var preText = text.substring(0, caret); const preText = text.substring(0, caret);
var lastSpace = preText.lastIndexOf(' '); const lastSpace = preText.lastIndexOf(' ');
var lastAt = preText.lastIndexOf('@'); const lastAt = preText.lastIndexOf('@');
if (caret > lastAt && lastSpace < lastAt) { if (caret > lastAt && lastSpace < lastAt) {
this.doProcessMentions = true; this.doProcessMentions = true;
@@ -136,18 +158,18 @@ module.exports = React.createClass({
} }
this.props.onKeyPress(e); this.props.onKeyPress(e);
}, }
handleKeyDown: function(e) { handleKeyDown(e) {
if (utils.getSelectedText(this.refs.message.getDOMNode()) !== '') { if (Utils.getSelectedText(React.findDOMNode(this.refs.message)) !== '') {
this.doProcessMentions = true; this.doProcessMentions = true;
} }
if (e.keyCode === 8) { if (e.keyCode === 8) {
this.handleBackspace(e); this.handleBackspace(e);
} }
}, }
handleBackspace: function() { handleBackspace() {
var text = this.refs.message.getDOMNode().value; const text = React.findDOMNode(this.refs.message).value;
if (text.indexOf('/') === 0) { if (text.indexOf('/') === 0) {
this.refs.commands.getSuggestedCommands(text.substring(0, text.length - 1)); this.refs.commands.getSuggestedCommands(text.substring(0, text.length - 1));
} }
@@ -156,21 +178,21 @@ module.exports = React.createClass({
return; return;
} }
var caret = utils.getCaretPosition(this.refs.message.getDOMNode()); const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message));
var preText = text.substring(0, caret); const preText = text.substring(0, caret);
var lastSpace = preText.lastIndexOf(' '); const lastSpace = preText.lastIndexOf(' ');
var lastAt = preText.lastIndexOf('@'); const lastAt = preText.lastIndexOf('@');
if (caret > lastAt && (lastSpace > lastAt || lastSpace === -1)) { if (caret > lastAt && (lastSpace > lastAt || lastSpace === -1)) {
this.doProcessMentions = true; this.doProcessMentions = true;
} }
}, }
checkForNewMention: function(text) { checkForNewMention(text) {
var caret = utils.getCaretPosition(this.refs.message.getDOMNode()); const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message));
var preText = text.substring(0, caret); const preText = text.substring(0, caret);
var atIndex = preText.lastIndexOf('@'); const atIndex = preText.lastIndexOf('@');
// The @ character not typed, so nothing to do. // The @ character not typed, so nothing to do.
if (atIndex === -1) { if (atIndex === -1) {
@@ -178,8 +200,8 @@ module.exports = React.createClass({
return; return;
} }
var lastCharSpace = preText.lastIndexOf(String.fromCharCode(160)); const lastCharSpace = preText.lastIndexOf(String.fromCharCode(160));
var lastSpace = preText.lastIndexOf(' '); const lastSpace = preText.lastIndexOf(' ');
// If there is a space after the last @, nothing to do. // If there is a space after the last @, nothing to do.
if (lastSpace > atIndex || lastCharSpace > atIndex) { if (lastSpace > atIndex || lastCharSpace > atIndex) {
@@ -188,43 +210,43 @@ module.exports = React.createClass({
} }
// Get the name typed so far. // Get the name typed so far.
var name = preText.substring(atIndex + 1, preText.length).toLowerCase(); const name = preText.substring(atIndex + 1, preText.length).toLowerCase();
this.updateMentionTab(name); this.updateMentionTab(name);
}, }
addMention: function(name) { addMention(name) {
var caret = utils.getCaretPosition(this.refs.message.getDOMNode()); const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message));
var text = this.props.messageText; const text = this.props.messageText;
var preText = text.substring(0, caret); const preText = text.substring(0, caret);
var atIndex = preText.lastIndexOf('@'); const atIndex = preText.lastIndexOf('@');
// The @ character not typed, so nothing to do. // The @ character not typed, so nothing to do.
if (atIndex === -1) { if (atIndex === -1) {
return; return;
} }
var prefix = text.substring(0, atIndex); const prefix = text.substring(0, atIndex);
var suffix = text.substring(caret, text.length); const suffix = text.substring(caret, text.length);
this.caret = prefix.length + name.length + 2; this.caret = prefix.length + name.length + 2;
this.addedMention = true; this.addedMention = true;
this.doProcessMentions = true; this.doProcessMentions = true;
this.props.onUserInput(prefix + '@' + name + ' ' + suffix); this.props.onUserInput(`${prefix}@${name} ${suffix}`);
}, }
addCommand: function(cmd) { addCommand(cmd) {
var elm = this.refs.message.getDOMNode(); const elm = React.findDOMNode(this.refs.message);
elm.value = cmd; elm.value = cmd;
this.handleChange(); this.handleChange();
}, }
resize: function() { resize() {
var e = this.refs.message.getDOMNode(); const e = React.findDOMNode(this.refs.message);
var w = this.refs.wrapper.getDOMNode(); const w = React.findDOMNode(this.refs.wrapper);
var lht = parseInt($(e).css('lineHeight'), 10); const lht = parseInt($(e).css('lineHeight'), 10);
var lines = e.scrollHeight / lht; const lines = e.scrollHeight / lht;
var mod = 15; let mod = 15;
if (lines < 2.5 || this.props.messageText === '') { if (lines < 2.5 || this.props.messageText === '') {
mod = 30; mod = 30;
@@ -237,28 +259,62 @@ module.exports = React.createClass({
$(e).css({height: 'auto', 'overflow-y': 'scroll'}).height(167); $(e).css({height: 'auto', 'overflow-y': 'scroll'}).height(167);
$(w).css({height: 'auto'}).height(167); $(w).css({height: 'auto'}).height(167);
} }
}, }
handleFocus: function() { handleFocus() {
var elm = this.refs.message.getDOMNode(); const elm = React.findDOMNode(this.refs.message);
if (elm.title === elm.value) { if (elm.title === elm.value) {
elm.value = ''; elm.value = '';
} }
}, }
handleBlur: function() { handleBlur() {
var elm = this.refs.message.getDOMNode(); const elm = React.findDOMNode(this.refs.message);
if (elm.value === '') { if (elm.value === '') {
elm.value = elm.title; elm.value = elm.title;
} }
}, }
handlePaste: function() { handlePaste() {
this.doProcessMentions = true; this.doProcessMentions = true;
}, }
render: function() { render() {
return ( return (
<div ref='wrapper' className='textarea-wrapper'> <div
<CommandList ref='commands' addCommand={this.addCommand} channelId={this.props.channelId} /> ref='wrapper'
<textarea id={this.props.id} ref='message' className={'form-control custom-textarea ' + this.state.connection} spellCheck='true' autoComplete='off' autoCorrect='off' rows='1' maxLength={Constants.MAX_POST_LEN} placeholder={this.props.createMessage} value={this.props.messageText} onInput={this.handleChange} onChange={this.handleChange} onKeyPress={this.handleKeyPress} onKeyDown={this.handleKeyDown} onFocus={this.handleFocus} onBlur={this.handleBlur} onPaste={this.handlePaste} /> className='textarea-wrapper'
>
<CommandList
ref='commands'
addCommand={this.addCommand}
channelId={this.props.channelId}
/>
<textarea
id={this.props.id}
ref='message'
className={`form-control custom-textarea ${this.state.connection}`}
spellCheck='true'
autoComplete='off'
autoCorrect='off'
rows='1'
maxLength={Constants.MAX_POST_LEN}
placeholder={this.props.createMessage}
value={this.props.messageText}
onInput={this.handleChange}
onChange={this.handleChange}
onKeyPress={this.handleKeyPress}
onKeyDown={this.handleKeyDown}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
onPaste={this.handlePaste}
/>
</div> </div>
); );
} }
}); }
Textbox.propTypes = {
id: React.PropTypes.string.isRequired,
channelId: React.PropTypes.string,
messageText: React.PropTypes.string.isRequired,
onUserInput: React.PropTypes.func.isRequired,
onKeyPress: React.PropTypes.func.isRequired,
createMessage: React.PropTypes.string.isRequired
};