Merge pull request #538 from hmhealey/mm2064
MM-2064 Cosmetic refactoring for ES6 and style guide
Этот коммит содержится в:
@@ -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'>×</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'>×</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">×</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'>×</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">×</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'>×</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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|||||||
203
web/react/components/member_list_team_item.jsx
Обычный файл
203
web/react/components/member_list_team_item.jsx
Обычный файл
@@ -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
|
||||||
|
};
|
||||||
80
web/react/components/popover_list_members.jsx
Обычный файл
80
web/react/components/popover_list_members.jsx
Обычный файл
@@ -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">×</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'>×</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'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'>×</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'>×</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
|
||||||
|
};
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user