summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--web/react/components/admin_console/user_item.jsx61
-rw-r--r--web/react/components/channel_invite_modal.jsx65
-rw-r--r--web/react/components/channel_members_modal.jsx113
-rw-r--r--web/react/components/confirm_modal.jsx10
-rw-r--r--web/react/components/filtered_user_list.jsx136
-rw-r--r--web/react/components/member_list.jsx56
-rw-r--r--web/react/components/member_list_item.jsx142
-rw-r--r--web/react/components/member_list_team.jsx26
-rw-r--r--web/react/components/more_direct_channels.jsx220
-rw-r--r--web/react/components/team_members_dropdown.jsx (renamed from web/react/components/member_list_team_item.jsx)141
-rw-r--r--web/react/components/team_members_modal.jsx41
-rw-r--r--web/react/components/user_list.jsx53
-rw-r--r--web/react/components/user_list_row.jsx65
-rw-r--r--web/react/utils/constants.jsx2
-rw-r--r--web/react/utils/utils.jsx4
-rw-r--r--web/sass-files/sass/partials/_admin-console.scss8
-rw-r--r--web/sass-files/sass/partials/_modal.scss55
-rw-r--r--web/sass-files/sass/partials/_settings.scss5
-rw-r--r--web/static/i18n/en.json37
-rw-r--r--web/static/i18n/es.json37
-rw-r--r--web/static/i18n/pt.json38
21 files changed, 595 insertions, 720 deletions
diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx
index 009a9f004..4af350bcd 100644
--- a/web/react/components/admin_console/user_item.jsx
+++ b/web/react/components/admin_console/user_item.jsx
@@ -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);
diff --git a/web/react/components/channel_invite_modal.jsx b/web/react/components/channel_invite_modal.jsx
index c9fe871d0..6c8d51abb 100644
--- a/web/react/components/channel_invite_modal.jsx
+++ b/web/react/components/channel_invite_modal.jsx
@@ -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>
diff --git a/web/react/components/channel_members_modal.jsx b/web/react/components/channel_members_modal.jsx
index fd452f206..688ab7dd2 100644
--- a/web/react/components/channel_members_modal.jsx
+++ b/web/react/components/channel_members_modal.jsx
@@ -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 {
}
);
}
- 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);
+ 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() {
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>
diff --git a/web/react/components/confirm_modal.jsx b/web/react/components/confirm_modal.jsx
index 987649f38..bb3576684 100644
--- a/web/react/components/confirm_modal.jsx
+++ b/web/react/components/confirm_modal.jsx
@@ -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
};
diff --git a/web/react/components/filtered_user_list.jsx b/web/react/components/filtered_user_list.jsx
new file mode 100644
index 000000000..ffd6ebf53
--- /dev/null
+++ b/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);
diff --git a/web/react/components/member_list.jsx b/web/react/components/member_list.jsx
deleted file mode 100644
index 62d600279..000000000
--- a/web/react/components/member_list.jsx
+++ /dev/null
@@ -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
-};
diff --git a/web/react/components/member_list_item.jsx b/web/react/components/member_list_item.jsx
deleted file mode 100644
index 88b98738d..000000000
--- a/web/react/components/member_list_item.jsx
+++ /dev/null
@@ -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
-};
diff --git a/web/react/components/member_list_team.jsx b/web/react/components/member_list_team.jsx
index f1c31131f..cfd5359b7 100644
--- a/web/react/components/member_list_team.jsx
+++ b/web/react/components/member_list_team.jsx
@@ -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
+};
diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx
index 3b72b251c..0814ac1b3 100644
--- a/web/react/components/more_direct_channels.jsx
+++ b/web/react/components/more_direct_channels.jsx
@@ -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);
diff --git a/web/react/components/member_list_team_item.jsx b/web/react/components/team_members_dropdown.jsx
index 23bc10781..8aacba8ca 100644
--- a/web/react/components/member_list_team_item.jsx
+++ b/web/react/components/team_members_dropdown.jsx
@@ -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);
diff --git a/web/react/components/team_members_modal.jsx b/web/react/components/team_members_modal.jsx
index 8ac435742..9bdb16438 100644
--- a/web/react/components/team_members_modal.jsx
+++ b/web/react/components/team_members_modal.jsx
@@ -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
diff --git a/web/react/components/user_list.jsx b/web/react/components/user_list.jsx
new file mode 100644
index 000000000..39453a827
--- /dev/null
+++ b/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)
+};
diff --git a/web/react/components/user_list_row.jsx b/web/react/components/user_list_row.jsx
new file mode 100644
index 000000000..2aeca7d47
--- /dev/null
+++ b/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)
+};
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 88f01a475..daea9f43e 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -437,6 +437,8 @@ export default {
Preferences: {
CATEGORY_DIRECT_CHANNEL_SHOW: 'direct_channel_show',
CATEGORY_DISPLAY_SETTINGS: 'display_settings',
+ DISPLAY_PREFER_NICKNAME: 'nickname_full_name',
+ DISPLAY_PREFER_FULL_NAME: 'full_name',
CATEGORY_ADVANCED_SETTINGS: 'advanced_settings',
TUTORIAL_STEP: 'tutorial_step'
},
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 3e531c821..6ab2f64d4 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -1082,9 +1082,9 @@ export function displayUsername(userId) {
let username = '';
if (user) {
- if (nameFormat === 'nickname_full_name') {
+ if (nameFormat === Constants.Preferences.DISPLAY_PREFER_NICKNAME) {
username = user.nickname || getFullName(user);
- } else if (nameFormat === 'full_name') {
+ } else if (nameFormat === Constants.Preferences.DISPLAY_PREFER_FULL_NAME) {
username = getFullName(user);
}
if (!username.trim().length) {
diff --git a/web/sass-files/sass/partials/_admin-console.scss b/web/sass-files/sass/partials/_admin-console.scss
index dc8b950e4..f782da36b 100644
--- a/web/sass-files/sass/partials/_admin-console.scss
+++ b/web/sass-files/sass/partials/_admin-console.scss
@@ -235,4 +235,12 @@
}
}
}
+
+ .member-list-holder {
+ .member-role, .member-drop {
+ position: absolute;
+ right: 15px;
+ top: 8px;
+ }
+ }
}
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index 09c366c77..ca83c77da 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -9,6 +9,7 @@
}
.more-table {
margin: 0;
+ table-layout: fixed;
p {
font-size: 0.9em;
overflow: hidden;
@@ -52,12 +53,6 @@
&.padding--equal {
padding: 8px;
}
- &.td--action {
- text-align: right;
- padding: 8px 15px 8px 8px;
- vertical-align: middle;
- position: relative;
- }
}
}
}
@@ -398,7 +393,6 @@
overflow-y: auto;
overflow-x: hidden;
margin-top: 10px;
- max-height: 500px;
position: relative;
}
@@ -434,3 +428,50 @@
max-height: 150px;
}
}
+
+.user-list {
+ display: flex;
+ flex-direction: column;
+
+ .profile-img {
+ width: 38px;
+ height: 38px;
+ flex-grow: 0;
+ flex-shrink: 0;
+ }
+
+ .user-list-item__details {
+ flex-grow: 1;
+ flex-shrink: 1;
+ overflow:hidden;
+ text-overflow: ellipsis;
+
+ .more-name {
+ white-space: nowrap;
+ }
+
+ .more-description {
+ white-space: nowrap;
+ }
+ }
+
+ .user-list-item__actions {
+ flex-grow: 0;
+ flex-shrink: 0;
+ }
+}
+
+.filtered-user-list {
+ display: flex;
+ flex-direction: column;
+
+ .filter-row {
+ flex-grow: 0;
+ flex-shrink: 0;
+ }
+
+ .user-list {
+ flex-grow: 1;
+ flex-shrink: 1;
+ }
+}
diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss
index 8508ce6fc..99b564fe5 100644
--- a/web/sass-files/sass/partials/_settings.scss
+++ b/web/sass-files/sass/partials/_settings.scss
@@ -399,10 +399,6 @@ h3 {
}
.member-role, .member-drop {
- position:absolute;
- right: 15px;
- top: 8px;
-
.fa {
margin-right: 5px;
@include opacity(0.5);
@@ -413,7 +409,6 @@ h3 {
top: -50%;
right: 110%;
}
-
}
.member-invite {
diff --git a/web/static/i18n/en.json b/web/static/i18n/en.json
index ce379e49c..14ef0fb46 100644
--- a/web/static/i18n/en.json
+++ b/web/static/i18n/en.json
@@ -544,6 +544,7 @@
"channel_info.notFound": "No Channel Found",
"channel_info.purpose": "Channel Purpose:",
"channel_info.url": "Channel URL:",
+ "channel_invite.add": " Add",
"channel_invite.addNewMembers": "Add New Members to ",
"channel_invite.close": "Close",
"channel_loader.connection_error": "There appears to be a problem with your internet connection.",
@@ -557,6 +558,7 @@
"channel_loader.wrote": " wrote: ",
"channel_members_modal.addNew": " Add New Members",
"channel_members_modal.close": "Close",
+ "channel_members_modal.removeMember": "Remove Member",
"channel_memebers_modal.members": " Members",
"channel_modal.cancel": "Cancel",
"channel_modal.channel": "Channel",
@@ -664,6 +666,9 @@
"file_upload.filesAbove": "Files above {max}MB could not be uploaded: {filenames}",
"file_upload.limited": "Uploads limited to {count} files maximum. Please use additional posts for more files.",
"file_upload.pasted": "Image Pasted at ",
+ "filtered_user_list.count": "{count, number} {count, plural, one {member} other {members}}",
+ "filtered_user_list.countTotal": "{count, number} {count, plural, one {member} other {members}} of {total} Total",
+ "filtered_user_list.search": "Search members",
"find_team.email": "Email",
"find_team.findDescription": "An email was sent with links to any teams to which you are a member.",
"find_team.findTitle": "Find Your Team",
@@ -761,23 +766,9 @@
"login_username.username": "Username",
"login_username.usernameReq": "A username is required",
"login_username.verifyEmailError": "Please verify your email address. Check your inbox for an email.",
- "member_item.add": " Add",
"member_item.makeAdmin": "Make Admin",
"member_item.member": "Member",
- "member_item.removeMember": "Remove Member",
"member_list.noUsersAdd": "No users to add.",
- "member_team_item.confirmDemoteDescription": "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.",
- "member_team_item.confirmDemoteRoleTitle": "Confirm demotion from System Admin role",
- "member_team_item.confirmDemotion": "Confirm Demotion",
- "member_team_item.confirmDemotionCmd": "platform -assign_role -team_name=\"yourteam\" -email=\"name@yourcompany.com\" -role=\"system_admin\"",
- "member_team_item.inactive": "Inactive",
- "member_team_item.makeActive": "Make Active",
- "member_team_item.makeAdmin": "Make Team Admin",
- "member_team_item.makeInactive": "Make Inactive",
- "member_team_item.makeMember": "Make Member",
- "member_team_item.member": "Member",
- "member_team_item.systemAdmin": "System Admin",
- "member_team_item.teamAdmin": "Team Admin",
"members_popover.msg": "Message",
"members_popover.title": "Members",
"more_channels.close": "Close",
@@ -787,12 +778,7 @@
"more_channels.noMore": "No more channels to join",
"more_channels.title": "More Channels",
"more_direct_channels.close": "Close",
- "more_direct_channels.count": "{count} {member}",
- "more_direct_channels.countTotal": "{count} {member} of {total} Total",
- "more_direct_channels.member": "Member",
"more_direct_channels.message": "Message",
- "more_direct_channels.notFound": "No users found :(",
- "more_direct_channels.search": "Search members",
"more_direct_channels.title": "Direct Messages",
"msg_typing.areTyping": "{users} and {last} are typing...",
"msg_typing.isTyping": "{user} is typing...",
@@ -994,6 +980,18 @@
"team_import_tab.summary": "View Summary",
"team_member_modal.close": "Close",
"team_member_modal.members": "{team} Members",
+ "team_members_dropdown.confirmDemoteDescription": "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.",
+ "team_members_dropdown.confirmDemoteRoleTitle": "Confirm demotion from System Admin role",
+ "team_members_dropdown.confirmDemotion": "Confirm Demotion",
+ "team_members_dropdown.confirmDemotionCmd": "platform -assign_role -team_name=\"yourteam\" -email=\"name@yourcompany.com\" -role=\"system_admin\"",
+ "team_members_dropdown.inactive": "Inactive",
+ "team_members_dropdown.makeActive": "Make Active",
+ "team_members_dropdown.makeAdmin": "Make Team Admin",
+ "team_members_dropdown.makeInactive": "Make Inactive",
+ "team_members_dropdown.makeMember": "Make Member",
+ "team_members_dropdown.member": "Member",
+ "team_members_dropdown.systemAdmin": "System Admin",
+ "team_members_dropdown.teamAdmin": "Team Admin",
"team_settings_modal.exportTab": "Export",
"team_settings_modal.generalTab": "General",
"team_settings_modal.importTab": "Import",
@@ -1282,6 +1280,7 @@
"user.settings.security.switchGoogle": "Switch to using Google SSO",
"user.settings.security.title": "Security Settings",
"user.settings.security.viewHistory": "View Access History",
+ "user_list.notFound": "No users found :(",
"user_profile.notShared": "Email not shared",
"view_image.loading": "Loading ",
"view_image_popover.download": "Download",
diff --git a/web/static/i18n/es.json b/web/static/i18n/es.json
index 2cf58dfb2..09abcf530 100644
--- a/web/static/i18n/es.json
+++ b/web/static/i18n/es.json
@@ -544,6 +544,7 @@
"channel_info.notFound": "Canal no encontrado",
"channel_info.purpose": "Propósito del Canal:",
"channel_info.url": "URL del Canal:",
+ "channel_invite.add": " Agregar",
"channel_invite.addNewMembers": "Agregar nuevos Miembros a ",
"channel_invite.close": "Cerrar",
"channel_loader.connection_error": "Parece haber un problema con tu conexión a internet.",
@@ -557,6 +558,7 @@
"channel_loader.wrote": " escribió: ",
"channel_members_modal.addNew": " Agregar nuevos Miembros",
"channel_members_modal.close": "Cerrar",
+ "channel_members_modal.removeMember": "Elminar Miembro",
"channel_memebers_modal.members": " Miembros",
"channel_modal.cancel": "Cancelar",
"channel_modal.channel": "Canal",
@@ -664,6 +666,9 @@
"file_upload.filesAbove": "No se pueden subir archivos de más de {max}MB: {filenames}",
"file_upload.limited": "Se pueden subir un máximo de {count} archivos. Por favor envía otros mensajes para adjuntar más archivos.",
"file_upload.pasted": "Imagen Pegada el ",
+ "filtered_user_list.count": "{count, number} {count, plural, one {Miembro} other {Miembros}}",
+ "filtered_user_list.countTotal": "{count, number} {count, plural, one {Miembro} other {Miembros}} de {total} Total",
+ "filtered_user_list.search": "Buscar miembros",
"find_team.email": "Correo electrónico",
"find_team.findDescription": "Enviamos un correo electrónico con los equipos a los que perteneces.",
"find_team.findTitle": "Encuentra tu equipo",
@@ -761,23 +766,9 @@
"login_username.username": "Nombre de usuario",
"login_username.usernameReq": "El nombre de usuario es obligatorio",
"login_username.verifyEmailError": "Por favor válida tu dirección de correo electrónico. Te hemos enviado un correo, revisa tu bandeja de entrada.",
- "member_item.add": " Agregar",
"member_item.makeAdmin": "Convertir en Admin de Equipo",
"member_item.member": "Miembro",
- "member_item.removeMember": "Elminar Miembro",
"member_list.noUsersAdd": "No hay usuarios que agregar.",
- "member_team_item.confirmDemoteDescription": "Si te degradas a ti mismo de la función de Administrador de Sistema y no hay otro usuario con privilegios de Administrador de Sistema, tendrás que volver a asignar un Administrador del Sistema accediendo al servidor de Mattermost a través de un terminal y ejecutar el siguiente comando.",
- "member_team_item.confirmDemoteRoleTitle": "Confirmar el decenso del rol de Administrador de Sistema",
- "member_team_item.confirmDemotion": "Confirmar decenso",
- "member_team_item.confirmDemotionCmd": "platform -assign_role -team_name=\"tuequipo\" -email=\"nombre@tuempresa.com\" -role=\"system_admin\"",
- "member_team_item.inactive": "Inactivo",
- "member_team_item.makeActive": "Activar",
- "member_team_item.makeAdmin": "Convertir a Admin de Equipo",
- "member_team_item.makeInactive": "Desactivar",
- "member_team_item.makeMember": "Convertir en Miembro",
- "member_team_item.member": "Miembro",
- "member_team_item.systemAdmin": "Administrador de Sistema",
- "member_team_item.teamAdmin": "Admin de Equipo",
"members_popover.msg": "Mensaje",
"members_popover.title": "Miembros",
"more_channels.close": "Cerrar",
@@ -787,12 +778,7 @@
"more_channels.noMore": "No hay más canales para unirse",
"more_channels.title": "Más Canales",
"more_direct_channels.close": "Cerrar",
- "more_direct_channels.count": "{count} {member}",
- "more_direct_channels.countTotal": "{count} {member} de {total} Total",
- "more_direct_channels.member": "Miembro",
"more_direct_channels.message": "Mensaje",
- "more_direct_channels.notFound": "No se encontraron usuarios :(",
- "more_direct_channels.search": "Buscar miembros",
"more_direct_channels.title": "Mensajes Directos",
"msg_typing.areTyping": "{users} y {last} están escribiendo...",
"msg_typing.isTyping": "{user} está escribiendo...",
@@ -994,6 +980,18 @@
"team_import_tab.summary": "Ver Resumen",
"team_member_modal.close": "Cerrar",
"team_member_modal.members": "{team} Miembros",
+ "team_members_modal.confirmDemoteDescription": "Si te degradas a ti mismo de la función de Administrador de Sistema y no hay otro usuario con privilegios de Administrador de Sistema, tendrás que volver a asignar un Administrador del Sistema accediendo al servidor de Mattermost a través de un terminal y ejecutar el siguiente comando.",
+ "team_members_modal.confirmDemoteRoleTitle": "Confirmar el decenso del rol de Administrador de Sistema",
+ "team_members_modal.confirmDemotion": "Confirmar decenso",
+ "team_members_modal.confirmDemotionCmd": "platform -assign_role -team_name=\"tuequipo\" -email=\"nombre@tuempresa.com\" -role=\"system_admin\"",
+ "team_members_modal.inactive": "Inactivo",
+ "team_members_modal.makeActive": "Activar",
+ "team_members_modal.makeAdmin": "Convertir a Admin de Equipo",
+ "team_members_modal.makeInactive": "Desactivar",
+ "team_members_modal.makeMember": "Convertir en Miembro",
+ "team_members_modal.member": "Miembro",
+ "team_members_modal.systemAdmin": "Administrador de Sistema",
+ "team_members_modal.teamAdmin": "Admin de Equipo",
"team_settings_modal.exportTab": "Exportar",
"team_settings_modal.generalTab": "General",
"team_settings_modal.importTab": "Importar",
@@ -1282,6 +1280,7 @@
"user.settings.security.switchGoogle": "Cambiar para utilizar Google SSO",
"user.settings.security.title": "Configuración de Seguridad",
"user.settings.security.viewHistory": "Visualizar historial de acceso",
+ "user_list.notFound": "No se encontraron usuarios :(",
"user_profile.notShared": "Correo no compartido",
"view_image.loading": "Cargando ",
"view_image_popover.download": "Descargar",
diff --git a/web/static/i18n/pt.json b/web/static/i18n/pt.json
index cd718e01b..ef5b9a2ab 100644
--- a/web/static/i18n/pt.json
+++ b/web/static/i18n/pt.json
@@ -544,6 +544,7 @@
"channel_info.notFound": "Nenhum Canal Encontrado",
"channel_info.purpose": "Propósito do Canal:",
"channel_info.url": "URL do Canal:",
+ "channel_invite.add": " Adicionar",
"channel_invite.addNewMembers": "Adicionar Novo Membro para ",
"channel_invite.close": "Fechar",
"channel_loader.posted": "Postado",
@@ -557,6 +558,7 @@
"channel_loader.unknown_error": "Recebido um código de status inesperado do servidor.",
"channel_members_modal.addNew": " Adicionar Novos Membros",
"channel_members_modal.close": "Fechar",
+ "channel_members_modal.removeMember": "Remover Membro",
"channel_memebers_modal.members": " Membros",
"channel_modal.cancel": "Cancelar",
"channel_modal.channel": "Canal",
@@ -664,6 +666,9 @@
"file_upload.filesAbove": "Arquivos acima {max}MB não podem ser enviados: {filenames}",
"file_upload.limited": "Limite máximo de uploads de {count} arquivos. Por favor use um post adicional para mais arquivos.",
"file_upload.pasted": "Imagem Colada em ",
+ "filtered_user_list.count": "{count, number} {count, plural, one {Membro} other {Membros}}",
+ "filtered_user_list.countTotal": "{count, number} {count, plural, one {Membro} other {Membros}} de {total} Total",
+ "filtered_user_list.search": "Procurar membros",
"find_team.email": "E-mail",
"find_team.findDescription": "Foi enviado um e-mail com links para todas as equipes do qual você é membro.",
"find_team.findTitle": "Encontre Sua Equipe",
@@ -761,23 +766,8 @@
"login_username.username": "Usuário",
"login_username.usernameReq": "Um nome de usuário é necessário",
"login_username.verifyEmailError": "Por favor verifique seu endereço de email. Verifique por um email na sua caixa de entrada.",
- "member_item.add": " Adicionar",
"member_item.makeAdmin": "Tornar Admin",
"member_item.member": "Membro",
- "member_item.removeMember": "Remover Membro",
- "member_list.noUsersAdd": "Nenhum usuário para adicionar.",
- "member_team_item.confirmDemoteDescription": "Se você rebaixar você mesmo de Admin de Sistema e não exista outro usuário como privilegios de Admin de Sistema, você precisa-rá re-inscrever um Admin de Sistema acessando o servidor Mattermost através do terminal e executando o seguinte comando.",
- "member_team_item.confirmDemoteRoleTitle": "Confirmar o rebaixamento de Admin Sistema",
- "member_team_item.confirmDemotion": "Confirmar Rebaixamento",
- "member_team_item.confirmDemotionCmd": "platform -assign_role -team_name=\"yourteam\" -email=\"name@yourcompany.com\" -role=\"system_admin\"",
- "member_team_item.inactive": "Inativo",
- "member_team_item.makeActive": "Tornar Ativo",
- "member_team_item.makeAdmin": "Tornar Admin de Equipe",
- "member_team_item.makeInactive": "Tornar Inativo",
- "member_team_item.makeMember": "Tornar Membro",
- "member_team_item.member": "Membro",
- "member_team_item.systemAdmin": "Admin do Sistema",
- "member_team_item.teamAdmin": "Admin Equipe",
"members_popover.msg": "Mensagem",
"members_popover.title": "Membros",
"more_channels.close": "Fechar",
@@ -787,12 +777,7 @@
"more_channels.noMore": "Não há mais canais para participar",
"more_channels.title": "Mais Canais",
"more_direct_channels.close": "Fechar",
- "more_direct_channels.count": "{count} {member}",
- "more_direct_channels.countTotal": "{count} {member} de {total} Total",
- "more_direct_channels.member": "Membro",
"more_direct_channels.message": "Mensagem",
- "more_direct_channels.notFound": "Nenhum usuário encontrado :(",
- "more_direct_channels.search": "Procurar membros",
"more_direct_channels.title": "Mensagens Diretas",
"msg_typing.areTyping": "{users} e {last} estão digitando...",
"msg_typing.isTyping": "{user} está digitando...",
@@ -994,6 +979,18 @@
"team_import_tab.summary": "Ver Resumo",
"team_member_modal.close": "Fechar",
"team_member_modal.members": "{team} Membros",
+ "team_members_dropdown.confirmDemoteDescription": "Se você rebaixar você mesmo de Admin de Sistema e não exista outro usuário como privilegios de Admin de Sistema, você precisa-rá re-inscrever um Admin de Sistema acessando o servidor Mattermost através do terminal e executando o seguinte comando.",
+ "team_members_dropdown.confirmDemoteRoleTitle": "Confirmar o rebaixamento de Admin Sistema",
+ "team_members_dropdown.confirmDemotion": "Confirmar Rebaixamento",
+ "team_members_dropdown.confirmDemotionCmd": "platform -assign_role -team_name=\"yourteam\" -email=\"name@yourcompany.com\" -role=\"system_admin\"",
+ "team_members_dropdown.inactive": "Inativo",
+ "team_members_dropdown.makeActive": "Tornar Ativo",
+ "team_members_dropdown.makeAdmin": "Tornar Admin de Equipe",
+ "team_members_dropdown.makeInactive": "Tornar Inativo",
+ "team_members_dropdown.makeMember": "Tornar Membro",
+ "team_members_dropdown.member": "Membro",
+ "team_members_dropdown.systemAdmin": "Admin do Sistema",
+ "team_members_dropdown.teamAdmin": "Admin Equipe",
"team_settings_modal.exportTab": "Exportar",
"team_settings_modal.generalTab": "Geral",
"team_settings_modal.importTab": "Importar",
@@ -1282,6 +1279,7 @@
"user.settings.security.switchGoogle": "Trocar para usar Google SSO",
"user.settings.security.title": "Configurações de Segurança",
"user.settings.security.viewHistory": "Ver Histórico de Acesso",
+ "user_list.notFound": "Nenhum usuário encontrado :(",
"user_profile.notShared": "E-mail não compartilhado",
"view_image.loading": "Carregando ",
"view_image_popover.download": "Download",