Merge pull request #2318 from hmhealey/plt1090
PLT-1090 Refactored modals that display users
Этот коммит содержится в:
@@ -7,26 +7,7 @@ import UserStore from '../../stores/user_store.jsx';
|
||||
import ConfirmModal from '../confirm_modal.jsx';
|
||||
import TeamStore from '../../stores/team_store.jsx';
|
||||
|
||||
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
|
||||
|
||||
var holders = defineMessages({
|
||||
confirmDemoteRoleTitle: {
|
||||
id: 'admin.user_item.confirmDemoteRoleTitle',
|
||||
defaultMessage: 'Confirm demotion from System Admin role'
|
||||
},
|
||||
confirmDemotion: {
|
||||
id: 'admin.user_item.confirmDemotion',
|
||||
defaultMessage: 'Confirm Demotion'
|
||||
},
|
||||
confirmDemoteDescription: {
|
||||
id: 'admin.user_item.confirmDemoteDescription',
|
||||
defaultMessage: 'If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you\'ll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command.'
|
||||
},
|
||||
confirmDemotionCmd: {
|
||||
id: 'admin.user_item.confirmDemotionCmd',
|
||||
defaultMessage: 'platform -assign_role -team_name="yourteam" -email="name@yourcompany.com" -role="system_admin"'
|
||||
}
|
||||
});
|
||||
import {FormattedMessage} from 'mm-intl';
|
||||
|
||||
export default class UserItem extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -336,15 +317,44 @@ export default class UserItem extends React.Component {
|
||||
);
|
||||
}
|
||||
const me = UserStore.getCurrentUser();
|
||||
const {formatMessage} = this.props.intl;
|
||||
let makeDemoteModal = null;
|
||||
if (this.props.user.id === me.id) {
|
||||
const title = (
|
||||
<FormattedMessage
|
||||
id='admin.user_item.confirmDemoteRoleTitle'
|
||||
defaultMessage='Confirm demotion from System Admin role'
|
||||
/>
|
||||
);
|
||||
|
||||
const message = (
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='admin.user_item.confirmDemoteDescription'
|
||||
defaultMessage="If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you\'ll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command."
|
||||
/>
|
||||
<br/>
|
||||
<br/>
|
||||
<FormattedMessage
|
||||
id='admin.user_item.confirmDemotionCmd'
|
||||
defaultMessage='platform -assign_role -team_name="yourteam" -email="name@yourcompany.com" -role="system_admin"'
|
||||
/>
|
||||
{serverError}
|
||||
</div>
|
||||
);
|
||||
|
||||
const confirmButton = (
|
||||
<FormattedMessage
|
||||
id='admin.user_item.confirmDemotion'
|
||||
defaultMessage='Confirm Demotion'
|
||||
/>
|
||||
);
|
||||
|
||||
makeDemoteModal = (
|
||||
<ConfirmModal
|
||||
show={this.state.showDemoteModal}
|
||||
title={formatMessage(holders.confirmDemoteRoleTitle)}
|
||||
message={[formatMessage(holders.confirmDemoteDescription), React.createElement('br'), React.createElement('br'), formatMessage(holders.confirmDemotionCmd), serverError]}
|
||||
confirm_button={formatMessage(holders.confirmDemotion)}
|
||||
title={title}
|
||||
message={message}
|
||||
confirmButton={confirmButton}
|
||||
onConfirm={this.handleDemoteSubmit}
|
||||
onCancel={this.handleDemoteCancel}
|
||||
/>
|
||||
@@ -405,10 +415,7 @@ export default class UserItem extends React.Component {
|
||||
}
|
||||
|
||||
UserItem.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
user: React.PropTypes.object.isRequired,
|
||||
refreshProfiles: React.PropTypes.func.isRequired,
|
||||
doPasswordReset: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(UserItem);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import MemberList from './member_list.jsx';
|
||||
import FilteredUserList from './filtered_user_list.jsx';
|
||||
import LoadingScreen from './loading_screen.jsx';
|
||||
|
||||
import UserStore from '../stores/user_store.jsx';
|
||||
@@ -22,8 +22,12 @@ export default class ChannelInviteModal extends React.Component {
|
||||
this.onListenerChange = this.onListenerChange.bind(this);
|
||||
this.handleInvite = this.handleInvite.bind(this);
|
||||
|
||||
this.createInviteButton = this.createInviteButton.bind(this);
|
||||
|
||||
// the state gets populated when the modal is shown
|
||||
this.state = {};
|
||||
this.state = {
|
||||
loading: true
|
||||
};
|
||||
}
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
if (!this.props.show && !nextProps.show) {
|
||||
@@ -77,19 +81,6 @@ export default class ChannelInviteModal extends React.Component {
|
||||
loading: false
|
||||
};
|
||||
}
|
||||
onShow() {
|
||||
if ($(window).width() > 768) {
|
||||
$(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
|
||||
$(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 200);
|
||||
} else {
|
||||
$(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 150);
|
||||
}
|
||||
}
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.show && !prevProps.show) {
|
||||
this.onShow();
|
||||
}
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!this.props.show && nextProps.show) {
|
||||
ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
|
||||
@@ -108,9 +99,10 @@ export default class ChannelInviteModal extends React.Component {
|
||||
this.setState(newState);
|
||||
}
|
||||
}
|
||||
handleInvite(userId) {
|
||||
var data = {};
|
||||
data.user_id = userId;
|
||||
handleInvite(user) {
|
||||
const data = {
|
||||
user_id: user.id
|
||||
};
|
||||
|
||||
Client.addChannelMember(
|
||||
this.props.channel.id,
|
||||
@@ -124,27 +116,40 @@ export default class ChannelInviteModal extends React.Component {
|
||||
}
|
||||
);
|
||||
}
|
||||
createInviteButton({user}) {
|
||||
return (
|
||||
<a
|
||||
onClick={this.handleInvite.bind(this, user)}
|
||||
className='btn btn-sm btn-primary'
|
||||
>
|
||||
<i className='glyphicon glyphicon-envelope'/>
|
||||
<FormattedMessage
|
||||
id='channel_invite.add'
|
||||
defaultMessage=' Add'
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
var inviteError = null;
|
||||
if (this.state.inviteError) {
|
||||
inviteError = (<label className='has-error control-label'>{this.state.inviteError}</label>);
|
||||
}
|
||||
|
||||
var currentMember = ChannelStore.getCurrentMember();
|
||||
var isAdmin = false;
|
||||
if (currentMember) {
|
||||
isAdmin = Utils.isAdmin(currentMember.roles) || Utils.isAdmin(UserStore.getCurrentUser().roles);
|
||||
}
|
||||
|
||||
var content;
|
||||
if (this.state.loading) {
|
||||
content = (<LoadingScreen/>);
|
||||
} else {
|
||||
let maxHeight = 1000;
|
||||
if (Utils.windowHeight() <= 1200) {
|
||||
maxHeight = Utils.windowHeight() - 300;
|
||||
}
|
||||
|
||||
content = (
|
||||
<MemberList
|
||||
memberList={this.state.nonmembers}
|
||||
isAdmin={isAdmin}
|
||||
handleInvite={this.handleInvite}
|
||||
<FilteredUserList
|
||||
style={{maxHeight}}
|
||||
users={this.state.nonmembers}
|
||||
actions={[this.createInviteButton]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -164,9 +169,7 @@ export default class ChannelInviteModal extends React.Component {
|
||||
<span className='name'>{this.props.channel.display_name}</span>
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body
|
||||
ref='modalBody'
|
||||
>
|
||||
<Modal.Body>
|
||||
{inviteError}
|
||||
{content}
|
||||
</Modal.Body>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import FilteredUserList from './filtered_user_list.jsx';
|
||||
import LoadingScreen from './loading_screen.jsx';
|
||||
import MemberList from './member_list.jsx';
|
||||
import ChannelInviteModal from './channel_invite_modal.jsx';
|
||||
|
||||
import UserStore from '../stores/user_store.jsx';
|
||||
@@ -24,6 +24,8 @@ export default class ChannelMembersModal extends React.Component {
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.handleRemove = this.handleRemove.bind(this);
|
||||
|
||||
this.createRemoveMemberButton = this.createRemoveMemberButton.bind(this);
|
||||
|
||||
// the rest of the state gets populated when the modal is shown
|
||||
this.state = {
|
||||
showInviteModal: false
|
||||
@@ -51,24 +53,10 @@ export default class ChannelMembersModal extends React.Component {
|
||||
};
|
||||
}
|
||||
|
||||
const users = UserStore.getActiveOnlyProfiles();
|
||||
const memberList = extraInfo.members;
|
||||
|
||||
const nonmemberList = [];
|
||||
for (const 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// clone the member list since we mutate it later on
|
||||
const memberList = extraInfo.members.map((member) => {
|
||||
return Object.assign({}, member);
|
||||
});
|
||||
|
||||
function compareByUsername(a, b) {
|
||||
if (a.username < b.username) {
|
||||
@@ -81,24 +69,12 @@ export default class ChannelMembersModal extends React.Component {
|
||||
}
|
||||
|
||||
memberList.sort(compareByUsername);
|
||||
nonmemberList.sort(compareByUsername);
|
||||
|
||||
return {
|
||||
nonmemberList,
|
||||
memberList,
|
||||
loading: false
|
||||
};
|
||||
}
|
||||
onShow() {
|
||||
if ($(window).width() > 768) {
|
||||
$(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
|
||||
}
|
||||
}
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.show && !prevProps.show) {
|
||||
this.onShow();
|
||||
}
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!this.props.show && nextProps.show) {
|
||||
ChannelStore.addExtraInfoChangeListener(this.onChange);
|
||||
@@ -116,41 +92,25 @@ export default class ChannelMembersModal extends React.Component {
|
||||
this.setState(newState);
|
||||
}
|
||||
}
|
||||
handleRemove(userId) {
|
||||
// Make sure the user is a member of the channel
|
||||
const memberList = this.state.memberList;
|
||||
let found = false;
|
||||
for (let i = 0; i < memberList.length; i++) {
|
||||
if (memberList[i].id === userId) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
handleRemove(user) {
|
||||
const userId = user.id;
|
||||
|
||||
const data = {};
|
||||
data.user_id = userId;
|
||||
|
||||
Client.removeChannelMember(ChannelStore.getCurrentId(), data,
|
||||
Client.removeChannelMember(
|
||||
ChannelStore.getCurrentId(),
|
||||
data,
|
||||
() => {
|
||||
let oldMember;
|
||||
const memberList = this.state.memberList.slice();
|
||||
for (let i = 0; i < memberList.length; i++) {
|
||||
if (userId === memberList[i].id) {
|
||||
oldMember = memberList[i];
|
||||
memberList.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const nonmemberList = this.state.nonmemberList;
|
||||
if (oldMember) {
|
||||
nonmemberList.push(oldMember);
|
||||
}
|
||||
|
||||
this.setState({memberList, nonmemberList});
|
||||
this.setState({memberList});
|
||||
AsyncClient.getChannelExtraInfo();
|
||||
},
|
||||
(err) => {
|
||||
@@ -158,30 +118,40 @@ export default class ChannelMembersModal extends React.Component {
|
||||
}
|
||||
);
|
||||
}
|
||||
createRemoveMemberButton({user}) {
|
||||
if (user.id === UserStore.getCurrentId()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type='button'
|
||||
className='btn btn-primary btn-message'
|
||||
onClick={this.handleRemove.bind(this, user)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='channel_members_modal.removeMember'
|
||||
defaultMessage='Remove Member'
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
var maxHeight = 1000;
|
||||
if (Utils.windowHeight() <= 1200) {
|
||||
maxHeight = Utils.windowHeight() - 300;
|
||||
}
|
||||
|
||||
const currentMember = ChannelStore.getCurrentMember();
|
||||
let isAdmin = false;
|
||||
if (currentMember) {
|
||||
isAdmin = Utils.isAdmin(currentMember.roles) || Utils.isAdmin(UserStore.getCurrentUser().roles);
|
||||
}
|
||||
|
||||
let content;
|
||||
if (this.state.loading) {
|
||||
content = (<LoadingScreen/>);
|
||||
} else {
|
||||
let maxHeight = 1000;
|
||||
if (Utils.windowHeight() <= 1200) {
|
||||
maxHeight = Utils.windowHeight() - 300;
|
||||
}
|
||||
|
||||
content = (
|
||||
<div className='team-member-list'>
|
||||
<MemberList
|
||||
memberList={this.state.memberList}
|
||||
isAdmin={isAdmin}
|
||||
handleRemove={this.handleRemove}
|
||||
/>
|
||||
</div>
|
||||
<FilteredUserList
|
||||
style={{maxHeight}}
|
||||
users={this.state.memberList}
|
||||
actions={[this.createRemoveMemberButton]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -217,7 +187,6 @@ export default class ChannelMembersModal extends React.Component {
|
||||
</Modal.Header>
|
||||
<Modal.Body
|
||||
ref='modalBody'
|
||||
style={{maxHeight}}
|
||||
>
|
||||
{content}
|
||||
</Modal.Body>
|
||||
|
||||
@@ -44,7 +44,7 @@ export default class ConfirmModal extends React.Component {
|
||||
className='btn btn-primary'
|
||||
onClick={this.props.onConfirm}
|
||||
>
|
||||
{this.props.confirm_button}
|
||||
{this.props.confirmButton}
|
||||
</button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
@@ -55,13 +55,13 @@ export default class ConfirmModal extends React.Component {
|
||||
ConfirmModal.defaultProps = {
|
||||
title: '',
|
||||
message: '',
|
||||
confirm_button: ''
|
||||
confirmButton: ''
|
||||
};
|
||||
ConfirmModal.propTypes = {
|
||||
show: React.PropTypes.bool.isRequired,
|
||||
title: React.PropTypes.string,
|
||||
message: React.PropTypes.string,
|
||||
confirm_button: React.PropTypes.string,
|
||||
title: React.PropTypes.node,
|
||||
message: React.PropTypes.node,
|
||||
confirmButton: React.PropTypes.node,
|
||||
onConfirm: React.PropTypes.func.isRequired,
|
||||
onCancel: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
136
web/react/components/filtered_user_list.jsx
Обычный файл
136
web/react/components/filtered_user_list.jsx
Обычный файл
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import UserList from './user_list.jsx';
|
||||
|
||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
|
||||
|
||||
const holders = defineMessages({
|
||||
member: {
|
||||
id: 'filtered_user_list.member',
|
||||
defaultMessage: 'Member'
|
||||
},
|
||||
search: {
|
||||
id: 'filtered_user_list.search',
|
||||
defaultMessage: 'Search members'
|
||||
}
|
||||
});
|
||||
|
||||
class FilteredUserList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleFilterChange = this.handleFilterChange.bind(this);
|
||||
|
||||
this.state = {
|
||||
filter: ''
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
$(ReactDOM.findDOMNode(this.refs.userList)).perfectScrollbar();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.filter !== this.state.filter) {
|
||||
$(ReactDOM.findDOMNode(this.refs.userList)).scrollTop(0);
|
||||
}
|
||||
}
|
||||
|
||||
handleFilterChange(e) {
|
||||
this.setState({
|
||||
filter: e.target.value
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {formatMessage} = this.props.intl;
|
||||
|
||||
let users = this.props.users;
|
||||
|
||||
if (this.state.filter) {
|
||||
const filter = this.state.filter.toLowerCase();
|
||||
|
||||
users = users.filter((user) => {
|
||||
return user.username.toLowerCase().indexOf(filter) !== -1 ||
|
||||
(user.first_name && user.first_name.toLowerCase().indexOf(filter) !== -1) ||
|
||||
(user.last_name && user.last_name.toLowerCase().indexOf(filter) !== -1) ||
|
||||
(user.nickname && user.nickname.toLowerCase().indexOf(filter) !== -1);
|
||||
});
|
||||
}
|
||||
|
||||
let count;
|
||||
if (users.length === this.props.users.length) {
|
||||
count = (
|
||||
<FormattedMessage
|
||||
id='filtered_user_list.count'
|
||||
defaultMessage='{count} {count, plural,
|
||||
one {member}
|
||||
other {members}
|
||||
}'
|
||||
values={{
|
||||
count: users.length
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
count = (
|
||||
<FormattedMessage
|
||||
id='filtered_user_list.countTotal'
|
||||
defaultMessage='{count} {count, plural,
|
||||
one {member}
|
||||
other {members}
|
||||
} of {total} Total'
|
||||
values={{
|
||||
count: users.length,
|
||||
total: this.props.users.length
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className='filtered-user-list'
|
||||
style={this.props.style}
|
||||
>
|
||||
<div className='filter-row'>
|
||||
<div className='col-sm-6'>
|
||||
<input
|
||||
ref='filter'
|
||||
className='form-control filter-textbox'
|
||||
placeholder={formatMessage(holders.search)}
|
||||
onInput={this.handleFilterChange}
|
||||
/>
|
||||
</div>
|
||||
<div className='col-sm-6'>
|
||||
<span className='member-count'>{count}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref='userList'
|
||||
className='user-list'
|
||||
>
|
||||
<UserList
|
||||
users={users}
|
||||
actions={this.props.actions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FilteredUserList.defaultProps = {
|
||||
users: [],
|
||||
actions: []
|
||||
};
|
||||
|
||||
FilteredUserList.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
users: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||
actions: React.PropTypes.arrayOf(React.PropTypes.func),
|
||||
style: React.PropTypes.object
|
||||
};
|
||||
|
||||
export default injectIntl(FilteredUserList);
|
||||
@@ -1,56 +0,0 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import MemberListItem from './member_list_item.jsx';
|
||||
|
||||
import {FormattedMessage} from 'mm-intl';
|
||||
|
||||
export default class MemberList extends React.Component {
|
||||
render() {
|
||||
var members = [];
|
||||
|
||||
if (this.props.memberList !== null) {
|
||||
members = this.props.memberList;
|
||||
}
|
||||
|
||||
var message = null;
|
||||
if (members.length === 0) {
|
||||
message = (
|
||||
<tr><td>
|
||||
<FormattedMessage
|
||||
id='member_list.noUsersAdd'
|
||||
defaultMessage='No users to add.'
|
||||
/>
|
||||
</td></tr>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<table className='table more-table member-list-holder'>
|
||||
<tbody>
|
||||
{members.map(function mymembers(member) {
|
||||
return (
|
||||
<MemberListItem
|
||||
key={member.id}
|
||||
member={member}
|
||||
isAdmin={this.props.isAdmin}
|
||||
handleInvite={this.props.handleInvite}
|
||||
handleRemove={this.props.handleRemove}
|
||||
handleMakeAdmin={this.props.handleMakeAdmin}
|
||||
/>
|
||||
);
|
||||
}, this)}
|
||||
{message}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MemberList.propTypes = {
|
||||
memberList: React.PropTypes.array,
|
||||
isAdmin: React.PropTypes.bool,
|
||||
handleInvite: React.PropTypes.func,
|
||||
handleRemove: React.PropTypes.func,
|
||||
handleMakeAdmin: React.PropTypes.func
|
||||
};
|
||||
@@ -1,142 +0,0 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import UserStore from '../stores/user_store.jsx';
|
||||
import * as Utils from '../utils/utils.jsx';
|
||||
|
||||
import {FormattedMessage} from 'mm-intl';
|
||||
|
||||
export default class MemberListItem extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleInvite = this.handleInvite.bind(this);
|
||||
this.handleRemove = this.handleRemove.bind(this);
|
||||
this.handleMakeAdmin = this.handleMakeAdmin.bind(this);
|
||||
}
|
||||
handleInvite(e) {
|
||||
e.preventDefault();
|
||||
this.props.handleInvite(this.props.member.id);
|
||||
}
|
||||
handleRemove(e) {
|
||||
e.preventDefault();
|
||||
this.props.handleRemove(this.props.member.id);
|
||||
}
|
||||
handleMakeAdmin(e) {
|
||||
e.preventDefault();
|
||||
this.props.handleMakeAdmin(this.props.member.id);
|
||||
}
|
||||
render() {
|
||||
var member = this.props.member;
|
||||
var isAdmin = this.props.isAdmin;
|
||||
var isMemberAdmin = Utils.isAdmin(member.roles);
|
||||
var timestamp = UserStore.getCurrentUser().update_at;
|
||||
|
||||
var invite;
|
||||
if (this.props.handleInvite) {
|
||||
invite = (
|
||||
<a
|
||||
onClick={this.handleInvite}
|
||||
className='btn btn-sm btn-primary'
|
||||
>
|
||||
<i className='glyphicon glyphicon-envelope'/>
|
||||
<FormattedMessage
|
||||
id='member_item.add'
|
||||
defaultMessage=' Add'
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
} else if (isAdmin && !isMemberAdmin && (member.id !== UserStore.getCurrentId())) {
|
||||
var self = this;
|
||||
|
||||
let makeAdminOption = null;
|
||||
if (this.props.handleMakeAdmin) {
|
||||
makeAdminOption = (
|
||||
<li role='presentation'>
|
||||
<a
|
||||
href=''
|
||||
role='menuitem'
|
||||
onClick={self.handleMakeAdmin}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='member_item.makeAdmin'
|
||||
defaultMessage='Make Admin'
|
||||
/>
|
||||
</a>
|
||||
</li>);
|
||||
}
|
||||
|
||||
let handleRemoveOption = null;
|
||||
if (this.props.handleRemove) {
|
||||
handleRemoveOption = (
|
||||
<li role='presentation'>
|
||||
<a
|
||||
href=''
|
||||
role='menuitem'
|
||||
onClick={self.handleRemove}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='member_item.removeMember'
|
||||
defaultMessage='Remove Member'
|
||||
/>
|
||||
</a>
|
||||
</li>);
|
||||
}
|
||||
|
||||
invite = (
|
||||
<div className='dropdown member-drop'>
|
||||
<a
|
||||
href='#'
|
||||
className='dropdown-toggle theme'
|
||||
type='button'
|
||||
data-toggle='dropdown'
|
||||
aria-expanded='true'
|
||||
>
|
||||
<span className='fa fa-pencil'></span>
|
||||
<span className='text-capitalize'>
|
||||
{member.roles ||
|
||||
<FormattedMessage
|
||||
id='member_item.member'
|
||||
defaultMessage='Member'
|
||||
/>
|
||||
}
|
||||
</span>
|
||||
</a>
|
||||
<ul
|
||||
className='dropdown-menu member-menu'
|
||||
role='menu'
|
||||
>
|
||||
{makeAdminOption}
|
||||
{handleRemoveOption}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
invite = (<div className='member-role text-capitalize'><span className='fa fa-pencil hidden'></span>{member.roles || <FormattedMessage id='member_item.member'/>}</div>);
|
||||
}
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td className='direct-channel'>
|
||||
<img
|
||||
className='profile-img pull-left'
|
||||
src={'/api/v1/users/' + member.id + '/image?time=' + timestamp + '&' + Utils.getSessionIndex()}
|
||||
height='36'
|
||||
width='36'
|
||||
/>
|
||||
<div className='more-name'>{Utils.displayUsername(member.id)}</div>
|
||||
<div className='more-description'>{member.email}</div>
|
||||
</td>
|
||||
<td className='td--action lg'>{invite}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MemberListItem.propTypes = {
|
||||
handleInvite: React.PropTypes.func,
|
||||
handleRemove: React.PropTypes.func,
|
||||
handleMakeAdmin: React.PropTypes.func,
|
||||
member: React.PropTypes.object,
|
||||
isAdmin: React.PropTypes.bool
|
||||
};
|
||||
@@ -1,7 +1,8 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import MemberListTeamItem from './member_list_team_item.jsx';
|
||||
import FilteredUserList from './filtered_user_list.jsx';
|
||||
import TeamMembersDropdown from './team_members_dropdown.jsx';
|
||||
import UserStore from '../stores/user_store.jsx';
|
||||
|
||||
export default class MemberListTeam extends React.Component {
|
||||
@@ -44,21 +45,16 @@ export default class MemberListTeam extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const memberList = this.state.users.map((user) => {
|
||||
return (
|
||||
<MemberListTeamItem
|
||||
key={user.id}
|
||||
user={user}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<table className='table more-table member-list-holder'>
|
||||
<tbody>
|
||||
{memberList}
|
||||
</tbody>
|
||||
</table>
|
||||
<FilteredUserList
|
||||
style={this.props.style}
|
||||
users={this.state.users}
|
||||
actions={[TeamMembersDropdown]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MemberListTeam.propTypes = {
|
||||
style: React.PropTypes.object
|
||||
};
|
||||
|
||||
@@ -2,36 +2,24 @@
|
||||
// See License.txt for license information.
|
||||
|
||||
const Modal = ReactBootstrap.Modal;
|
||||
import FilteredUserList from './filtered_user_list.jsx';
|
||||
import UserStore from '../stores/user_store.jsx';
|
||||
import * as Utils from '../utils/utils.jsx';
|
||||
|
||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
|
||||
import {FormattedMessage} from 'mm-intl';
|
||||
|
||||
const holders = defineMessages({
|
||||
member: {
|
||||
id: 'more_direct_channels.member',
|
||||
defaultMessage: 'Member'
|
||||
},
|
||||
search: {
|
||||
id: 'more_direct_channels.search',
|
||||
defaultMessage: 'Search members'
|
||||
}
|
||||
});
|
||||
|
||||
class MoreDirectChannels extends React.Component {
|
||||
export default class MoreDirectChannels extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleFilterChange = this.handleFilterChange.bind(this);
|
||||
this.handleHide = this.handleHide.bind(this);
|
||||
this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this);
|
||||
this.handleUserChange = this.handleUserChange.bind(this);
|
||||
|
||||
this.createRowForUser = this.createRowForUser.bind(this);
|
||||
this.createJoinDirectChannelButton = this.createJoinDirectChannelButton.bind(this);
|
||||
|
||||
this.state = {
|
||||
users: this.getUsersFromStore(),
|
||||
filter: '',
|
||||
loadingDMChannel: -1
|
||||
};
|
||||
}
|
||||
@@ -60,39 +48,10 @@ class MoreDirectChannels extends React.Component {
|
||||
UserStore.removeChangeListener(this.handleUserChange);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!prevProps.show && this.props.show) {
|
||||
this.onShow();
|
||||
}
|
||||
}
|
||||
|
||||
onShow() {
|
||||
if (Utils.isMobile()) {
|
||||
$(ReactDOM.findDOMNode(this.refs.userList)).css('max-height', $(window).height() - 250);
|
||||
} else {
|
||||
$(ReactDOM.findDOMNode(this.refs.userList)).perfectScrollbar();
|
||||
$(ReactDOM.findDOMNode(this.refs.userList)).css('max-height', $(window).height() - 300);
|
||||
}
|
||||
}
|
||||
|
||||
handleFilterChange() {
|
||||
const filter = ReactDOM.findDOMNode(this.refs.filter).value;
|
||||
|
||||
if ($(window).width() > 768) {
|
||||
$(ReactDOM.findDOMNode(this.refs.userList)).scrollTop(0);
|
||||
}
|
||||
|
||||
if (filter !== this.state.filter) {
|
||||
this.setState({filter});
|
||||
}
|
||||
}
|
||||
|
||||
handleHide() {
|
||||
if (this.props.onModalDismissed) {
|
||||
this.props.onModalDismissed();
|
||||
}
|
||||
|
||||
this.setState({filter: ''});
|
||||
}
|
||||
|
||||
handleShowDirectChannel(teammate, e) {
|
||||
@@ -120,145 +79,39 @@ class MoreDirectChannels extends React.Component {
|
||||
this.setState({users: this.getUsersFromStore()});
|
||||
}
|
||||
|
||||
createRowForUser(user) {
|
||||
const details = [];
|
||||
|
||||
const fullName = Utils.getFullName(user);
|
||||
if (fullName) {
|
||||
details.push(
|
||||
<span
|
||||
key={`${user.id}__full-name`}
|
||||
className='full-name'
|
||||
>
|
||||
{fullName}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (user.nickname) {
|
||||
const separator = fullName ? ' - ' : '';
|
||||
details.push(
|
||||
<span
|
||||
key={`${user.nickname}__nickname`}
|
||||
>
|
||||
{separator + user.nickname}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
let joinButton;
|
||||
createJoinDirectChannelButton({user}) {
|
||||
if (this.state.loadingDMChannel === user.id) {
|
||||
joinButton = (
|
||||
return (
|
||||
<img
|
||||
className='channel-loading-gif'
|
||||
src='/static/images/load.gif'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
joinButton = (
|
||||
<button
|
||||
type='button'
|
||||
className='btn btn-primary btn-message'
|
||||
onClick={this.handleShowDirectChannel.bind(this, user)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='more_direct_channels.message'
|
||||
defaultMessage='Message'
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={'direct-channel-row-user' + user.id}>
|
||||
<td
|
||||
key={user.id}
|
||||
className='direct-channel'
|
||||
>
|
||||
<img
|
||||
className='profile-img pull-left'
|
||||
width='38'
|
||||
height='38'
|
||||
src={`/api/v1/users/${user.id}/image?time=${user.update_at}&${Utils.getSessionIndex()}`}
|
||||
/>
|
||||
<div className='more-name'>
|
||||
{user.username}
|
||||
</div>
|
||||
<div className='more-description'>
|
||||
{details}
|
||||
</div>
|
||||
</td>
|
||||
<td className='td--action lg'>
|
||||
{joinButton}
|
||||
</td>
|
||||
</tr>
|
||||
<button
|
||||
type='button'
|
||||
className='btn btn-primary btn-message'
|
||||
onClick={this.handleShowDirectChannel.bind(this, user)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='more_direct_channels.message'
|
||||
defaultMessage='Message'
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {formatMessage} = this.props.intl;
|
||||
if (!this.props.show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let users = this.state.users;
|
||||
if (this.state.filter) {
|
||||
const filter = this.state.filter.toLowerCase();
|
||||
|
||||
users = users.filter((user) => {
|
||||
return user.username.toLowerCase().indexOf(filter) !== -1 ||
|
||||
user.first_name.toLowerCase().indexOf(filter) !== -1 ||
|
||||
user.last_name.toLowerCase().indexOf(filter) !== -1 ||
|
||||
user.nickname.toLowerCase().indexOf(filter) !== -1;
|
||||
});
|
||||
}
|
||||
|
||||
const userEntries = users.map(this.createRowForUser);
|
||||
|
||||
if (userEntries.length === 0) {
|
||||
userEntries.push(
|
||||
<tr key='no-users-found'><td>
|
||||
<FormattedMessage
|
||||
id='more_direct_channels.notFound'
|
||||
defaultMessage='No users found :('
|
||||
/>
|
||||
</td></tr>);
|
||||
}
|
||||
|
||||
let memberString = formatMessage(holders.member);
|
||||
if (users.length !== 1) {
|
||||
memberString += 's';
|
||||
}
|
||||
|
||||
let count;
|
||||
if (users.length === this.state.users.length) {
|
||||
count = (
|
||||
<FormattedMessage
|
||||
id='more_direct_channels.count'
|
||||
defaultMessage='{count} {member}'
|
||||
values={{
|
||||
count: users.length,
|
||||
member: memberString
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
count = (
|
||||
<FormattedMessage
|
||||
id='more_direct_channels.countTotal'
|
||||
defaultMessage='{count} {member} of {total} Total'
|
||||
values={{
|
||||
count: users.length,
|
||||
member: memberString,
|
||||
total: this.state.users.length
|
||||
}}
|
||||
/>
|
||||
);
|
||||
let maxHeight = 1000;
|
||||
if (Utils.windowHeight() <= 1200) {
|
||||
maxHeight = Utils.windowHeight() - 300;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
dialogClassName='more-modal'
|
||||
dialogClassName='more-modal more-direct-channels'
|
||||
show={this.props.show}
|
||||
onHide={this.handleHide}
|
||||
>
|
||||
@@ -270,30 +123,12 @@ class MoreDirectChannels extends React.Component {
|
||||
/>
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body ref='modalBody'>
|
||||
<div className='filter-row'>
|
||||
<div className='col-sm-6'>
|
||||
<input
|
||||
ref='filter'
|
||||
className='form-control filter-textbox'
|
||||
placeholder={formatMessage(holders.search)}
|
||||
onInput={this.handleFilterChange}
|
||||
/>
|
||||
</div>
|
||||
<div className='col-sm-6'>
|
||||
<span className='member-count'>{count}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref='userList'
|
||||
className='user-list'
|
||||
>
|
||||
<table className='more-table table'>
|
||||
<tbody>
|
||||
{userEntries}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<Modal.Body>
|
||||
<FilteredUserList
|
||||
style={{maxHeight}}
|
||||
users={this.state.users}
|
||||
actions={[this.createJoinDirectChannelButton]}
|
||||
/>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<button
|
||||
@@ -313,9 +148,6 @@ class MoreDirectChannels extends React.Component {
|
||||
}
|
||||
|
||||
MoreDirectChannels.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
show: React.PropTypes.bool.isRequired,
|
||||
onModalDismissed: React.PropTypes.func
|
||||
};
|
||||
|
||||
export default injectIntl(MoreDirectChannels);
|
||||
|
||||
@@ -9,28 +9,9 @@ import * as Utils from '../utils/utils.jsx';
|
||||
import ConfirmModal from './confirm_modal.jsx';
|
||||
import TeamStore from '../stores/team_store.jsx';
|
||||
|
||||
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
|
||||
import {FormattedMessage} from 'mm-intl';
|
||||
|
||||
var holders = defineMessages({
|
||||
confirmDemoteRoleTitle: {
|
||||
id: 'member_team_item.confirmDemoteRoleTitle',
|
||||
defaultMessage: 'Confirm demotion from System Admin role'
|
||||
},
|
||||
confirmDemotion: {
|
||||
id: 'member_team_item.confirmDemotion',
|
||||
defaultMessage: 'Confirm Demotion'
|
||||
},
|
||||
confirmDemoteDescription: {
|
||||
id: 'member_team_item.confirmDemoteDescription',
|
||||
defaultMessage: 'If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you\'ll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command.'
|
||||
},
|
||||
confirmDemotionCmd: {
|
||||
id: 'member_team_item.confirmDemotionCmd',
|
||||
defaultMessage: 'platform -assign_role -team_name="yourteam" -email="name@yourcompany.com" -role="system_admin"'
|
||||
}
|
||||
});
|
||||
|
||||
export default class MemberListTeamItem extends React.Component {
|
||||
export default class TeamMembersDropdown extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -159,24 +140,23 @@ export default class MemberListTeamItem extends React.Component {
|
||||
const user = this.props.user;
|
||||
let currentRoles = (
|
||||
<FormattedMessage
|
||||
id='member_team_item.member'
|
||||
id='team_members_dropdown.member'
|
||||
defaultMessage='Member'
|
||||
/>
|
||||
);
|
||||
const timestamp = UserStore.getCurrentUser().update_at;
|
||||
|
||||
if (user.roles.length > 0) {
|
||||
if (Utils.isSystemAdmin(user.roles)) {
|
||||
currentRoles = (
|
||||
<FormattedMessage
|
||||
id='member_team_item.systemAdmin'
|
||||
id='team_members_dropdown.systemAdmin'
|
||||
defaultMessage='System Admin'
|
||||
/>
|
||||
);
|
||||
} else if (Utils.isAdmin(user.roles)) {
|
||||
currentRoles = (
|
||||
<FormattedMessage
|
||||
id='member_team_item.teamAdmin'
|
||||
id='team_members_dropdown.teamAdmin'
|
||||
defaultMessage='Team Admin'
|
||||
/>
|
||||
);
|
||||
@@ -185,7 +165,6 @@ export default class MemberListTeamItem extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
const email = user.email;
|
||||
let showMakeMember = user.roles === 'admin' || user.roles === 'system_admin';
|
||||
let showMakeAdmin = user.roles === '' || user.roles === 'system_admin';
|
||||
let showMakeActive = false;
|
||||
@@ -194,7 +173,7 @@ export default class MemberListTeamItem extends React.Component {
|
||||
if (user.delete_at > 0) {
|
||||
currentRoles = (
|
||||
<FormattedMessage
|
||||
id='member_team_item.inactive'
|
||||
id='team_members_dropdown.inactive'
|
||||
defaultMessage='Inactive'
|
||||
/>
|
||||
);
|
||||
@@ -214,7 +193,7 @@ export default class MemberListTeamItem extends React.Component {
|
||||
onClick={this.handleMakeAdmin}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='member_team_item.makeAdmin'
|
||||
id='team_members_dropdown.makeAdmin'
|
||||
defaultMessage='Make Team Admin'
|
||||
/>
|
||||
</a>
|
||||
@@ -232,7 +211,7 @@ export default class MemberListTeamItem extends React.Component {
|
||||
onClick={this.handleMakeMember}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='member_team_item.makeMember'
|
||||
id='team_members_dropdown.makeMember'
|
||||
defaultMessage='Make Member'
|
||||
/>
|
||||
</a>
|
||||
@@ -250,7 +229,7 @@ export default class MemberListTeamItem extends React.Component {
|
||||
onClick={this.handleMakeActive}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='member_team_item.makeActive'
|
||||
id='team_members_dropdown.makeActive'
|
||||
defaultMessage='Make Active'
|
||||
/>
|
||||
</a>
|
||||
@@ -268,7 +247,7 @@ export default class MemberListTeamItem extends React.Component {
|
||||
onClick={this.handleMakeNotActive}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='member_team_item.makeInactive'
|
||||
id='team_members_dropdown.makeInactive'
|
||||
defaultMessage='Make Inactive'
|
||||
/>
|
||||
</a>
|
||||
@@ -276,15 +255,44 @@ export default class MemberListTeamItem extends React.Component {
|
||||
);
|
||||
}
|
||||
const me = UserStore.getCurrentUser();
|
||||
const {formatMessage} = this.props.intl;
|
||||
let makeDemoteModal = null;
|
||||
if (this.props.user.id === me.id) {
|
||||
const title = (
|
||||
<FormattedMessage
|
||||
id='team_members_dropdown.confirmDemoteRoleTitle'
|
||||
defaultMessage='Confirm demotion from System Admin role'
|
||||
/>
|
||||
);
|
||||
|
||||
const message = (
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='team_members_dropdown.confirmDemoteDescription'
|
||||
defaultMessage="If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you'll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command."
|
||||
/>
|
||||
<br/>
|
||||
<br/>
|
||||
<FormattedMessage
|
||||
id='team_members_dropdown.confirmDemotionCmd'
|
||||
defaultMessage='platform -assign_role -team_name="yourteam" -email="name@yourcompany.com" -role="system_admin"'
|
||||
/>
|
||||
{serverError}
|
||||
</div>
|
||||
);
|
||||
|
||||
const confirmButton = (
|
||||
<FormattedMessage
|
||||
id='team_members_dropdown.confirmDemotion'
|
||||
defaultMessage='Confirm Demotion'
|
||||
/>
|
||||
);
|
||||
|
||||
makeDemoteModal = (
|
||||
<ConfirmModal
|
||||
show={this.state.showDemoteModal}
|
||||
title={formatMessage(holders.confirmDemoteRoleTitle)}
|
||||
message={[formatMessage(holders.confirmDemoteDescription), React.createElement('br'), React.createElement('br'), formatMessage(holders.confirmDemotionCmd), serverError]}
|
||||
confirm_button={formatMessage(holders.confirmDemotion)}
|
||||
title={title}
|
||||
message={message}
|
||||
confirmButton={confirmButton}
|
||||
onConfirm={this.handleDemoteSubmit}
|
||||
onCancel={this.handleDemoteCancel}
|
||||
/>
|
||||
@@ -292,48 +300,33 @@ export default class MemberListTeamItem extends React.Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td className='row member-div'>
|
||||
<img
|
||||
className='post-profile-img pull-left'
|
||||
src={`/api/v1/users/${user.id}/image?time=${timestamp}&${Utils.getSessionIndex()}`}
|
||||
height='36'
|
||||
width='36'
|
||||
/>
|
||||
<span className='more-name'>{Utils.displayUsername(user.id)}</span>
|
||||
<span className='more-description'>{email}</span>
|
||||
<div className='dropdown member-drop'>
|
||||
<a
|
||||
href='#'
|
||||
className='dropdown-toggle theme'
|
||||
type='button'
|
||||
data-toggle='dropdown'
|
||||
aria-expanded='true'
|
||||
>
|
||||
<span className='fa fa-pencil'></span>
|
||||
<span>{currentRoles} </span>
|
||||
</a>
|
||||
<ul
|
||||
className='dropdown-menu member-menu'
|
||||
role='menu'
|
||||
>
|
||||
{makeAdmin}
|
||||
{makeMember}
|
||||
{makeActive}
|
||||
{makeNotActive}
|
||||
</ul>
|
||||
</div>
|
||||
{makeDemoteModal}
|
||||
{serverError}
|
||||
</td>
|
||||
</tr>
|
||||
<div className='dropdown member-drop'>
|
||||
<a
|
||||
href='#'
|
||||
className='dropdown-toggle theme'
|
||||
type='button'
|
||||
data-toggle='dropdown'
|
||||
aria-expanded='true'
|
||||
>
|
||||
<span className='fa fa-pencil'></span>
|
||||
<span>{currentRoles} </span>
|
||||
</a>
|
||||
<ul
|
||||
className='dropdown-menu member-menu'
|
||||
role='menu'
|
||||
>
|
||||
{makeAdmin}
|
||||
{makeMember}
|
||||
{makeActive}
|
||||
{makeNotActive}
|
||||
</ul>
|
||||
{makeDemoteModal}
|
||||
{serverError}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MemberListTeamItem.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
TeamMembersDropdown.propTypes = {
|
||||
user: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(MemberListTeamItem);
|
||||
@@ -3,45 +3,24 @@
|
||||
|
||||
import MemberListTeam from './member_list_team.jsx';
|
||||
import TeamStore from '../stores/team_store.jsx';
|
||||
import * as Utils from '../utils/utils.jsx';
|
||||
|
||||
import {FormattedMessage} from 'mm-intl';
|
||||
|
||||
const Modal = ReactBootstrap.Modal;
|
||||
|
||||
export default class TeamMembersModal extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onShow = this.onShow.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.show) {
|
||||
this.onShow();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.show && !prevProps.show) {
|
||||
this.onShow();
|
||||
}
|
||||
}
|
||||
|
||||
onShow() {
|
||||
if ($(window).width() > 768) {
|
||||
$(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
|
||||
$(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 200);
|
||||
} else {
|
||||
$(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 150);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const team = TeamStore.getCurrent();
|
||||
|
||||
let maxHeight = 1000;
|
||||
if (Utils.windowHeight() <= 1200) {
|
||||
maxHeight = Utils.windowHeight() - 300;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
dialogClassName='team-members-modal'
|
||||
dialogClassName='more-modal'
|
||||
show={this.props.show}
|
||||
onHide={this.props.onHide}
|
||||
>
|
||||
@@ -54,10 +33,8 @@ export default class TeamMembersModal extends React.Component {
|
||||
}}
|
||||
/>
|
||||
</Modal.Header>
|
||||
<Modal.Body ref='modalBody'>
|
||||
<div className='team-member-list'>
|
||||
<MemberListTeam/>
|
||||
</div>
|
||||
<Modal.Body>
|
||||
<MemberListTeam style={{maxHeight}}/>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<button
|
||||
|
||||
53
web/react/components/user_list.jsx
Обычный файл
53
web/react/components/user_list.jsx
Обычный файл
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {FormattedMessage} from 'mm-intl';
|
||||
import UserListRow from './user_list_row.jsx';
|
||||
|
||||
export default class UserList extends React.Component {
|
||||
render() {
|
||||
const users = this.props.users;
|
||||
|
||||
let content;
|
||||
if (users.length > 0) {
|
||||
content = users.map((user) => {
|
||||
return (
|
||||
<UserListRow
|
||||
key={user.id}
|
||||
user={user}
|
||||
actions={this.props.actions}
|
||||
/>
|
||||
);
|
||||
});
|
||||
} else {
|
||||
content = (
|
||||
<tr key='no-users-found'>
|
||||
<td>
|
||||
<FormattedMessage
|
||||
id='user_list.notFound'
|
||||
defaultMessage='No users found :('
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<table className='more-table table'>
|
||||
<tbody>
|
||||
{content}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UserList.defaultProps = {
|
||||
users: [],
|
||||
actions: []
|
||||
};
|
||||
|
||||
UserList.propTypes = {
|
||||
users: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||
actions: React.PropTypes.arrayOf(React.PropTypes.func)
|
||||
};
|
||||
65
web/react/components/user_list_row.jsx
Обычный файл
65
web/react/components/user_list_row.jsx
Обычный файл
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import Constants from '../utils/constants.jsx';
|
||||
import PreferenceStore from '../stores/preference_store.jsx';
|
||||
import * as Utils from '../utils/utils.jsx';
|
||||
|
||||
export default function UserListRow({user, actions}) {
|
||||
const nameFormat = PreferenceStore.get(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', '');
|
||||
|
||||
let name = user.username;
|
||||
if (user.nickname && nameFormat === Constants.Preferences.DISPLAY_PREFER_NICKNAME) {
|
||||
name = `${user.nickname} (${user.username})`;
|
||||
} else if ((user.first_name || user.last_name) && (nameFormat === Constants.Preferences.DISPLAY_PREFER_NICKNAME || nameFormat === Constants.Preferences.DISPLAY_PREFER_FULL_NAME)) {
|
||||
name = `${Utils.getFullName(user)} (${user.username})`;
|
||||
}
|
||||
|
||||
const buttons = actions.map((Action, index) => {
|
||||
return (
|
||||
<Action
|
||||
key={index.toString()}
|
||||
user={user}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td
|
||||
key={user.id}
|
||||
className='direct-channel'
|
||||
style={{display: 'flex'}}
|
||||
>
|
||||
<img
|
||||
className='profile-img'
|
||||
src={`/api/v1/users/${user.id}/image?time=${user.update_at}&${Utils.getSessionIndex()}`}
|
||||
/>
|
||||
<div
|
||||
className='user-list-item__details'
|
||||
>
|
||||
<div className='more-name'>
|
||||
{name}
|
||||
</div>
|
||||
<div className='more-description'>
|
||||
{user.email}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className='user-list-item__actions'
|
||||
>
|
||||
{buttons}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
UserListRow.defaultProps = {
|
||||
actions: []
|
||||
};
|
||||
|
||||
UserListRow.propTypes = {
|
||||
user: React.PropTypes.object.isRequired,
|
||||
actions: React.PropTypes.arrayOf(React.PropTypes.func)
|
||||
};
|
||||
Ссылка в новой задаче
Block a user