diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx index 009a9f0048..4af350bcde 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 = ( + + ); + + const message = ( +
+ +
+
+ + {serverError} +
+ ); + + const confirmButton = ( + + ); + makeDemoteModal = ( @@ -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 c9fe871d06..6c8d51abb9 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 ( + + + + + ); + } render() { var inviteError = null; if (this.state.inviteError) { inviteError = (); } - 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 = (); } else { + let maxHeight = 1000; + if (Utils.windowHeight() <= 1200) { + maxHeight = Utils.windowHeight() - 300; + } + content = ( - ); } @@ -164,9 +169,7 @@ export default class ChannelInviteModal extends React.Component { {this.props.channel.display_name} - + {inviteError} {content} diff --git a/web/react/components/channel_members_modal.jsx b/web/react/components/channel_members_modal.jsx index fd452f2064..688ab7dd2f 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 { } ); } + createRemoveMemberButton({user}) { + if (user.id === UserStore.getCurrentId()) { + return null; + } + + return ( + + ); + } 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 = (); } else { + let maxHeight = 1000; + if (Utils.windowHeight() <= 1200) { + maxHeight = Utils.windowHeight() - 300; + } + content = ( -
- -
+ ); } @@ -217,7 +187,6 @@ export default class ChannelMembersModal extends React.Component { {content} diff --git a/web/react/components/confirm_modal.jsx b/web/react/components/confirm_modal.jsx index 987649f387..bb35766842 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} @@ -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 0000000000..ffd6ebf530 --- /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 = ( + + ); + } else { + count = ( + + ); + } + + return ( +
+
+
+ +
+
+ {count} +
+
+
+ +
+
+ ); + } +} + +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 62d6002799..0000000000 --- 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 = ( - - - - ); - } - - return ( - - - {members.map(function mymembers(member) { - return ( - - ); - }, this)} - {message} - -
- ); - } -} - -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 88b98738d1..0000000000 --- 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 = ( - - - - - ); - } else if (isAdmin && !isMemberAdmin && (member.id !== UserStore.getCurrentId())) { - var self = this; - - let makeAdminOption = null; - if (this.props.handleMakeAdmin) { - makeAdminOption = ( -
  • - - - -
  • ); - } - - let handleRemoveOption = null; - if (this.props.handleRemove) { - handleRemoveOption = ( -
  • - - - -
  • ); - } - - invite = ( -
    - - - - {member.roles || - - } - - -
      - {makeAdminOption} - {handleRemoveOption} -
    -
    - ); - } else { - invite = (
    {member.roles || }
    ); - } - - return ( - - - -
    {Utils.displayUsername(member.id)}
    -
    {member.email}
    - - {invite} - - ); - } -} - -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 f1c31131f5..cfd5359b79 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 ( - - ); - }); - return ( - - - {memberList} - -
    + ); } } + +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 3b72b251c3..0814ac1b3e 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( - - {fullName} - - ); - } - - if (user.nickname) { - const separator = fullName ? ' - ' : ''; - details.push( - - {separator + user.nickname} - - ); - } - - let joinButton; + createJoinDirectChannelButton({user}) { if (this.state.loadingDMChannel === user.id) { - joinButton = ( + return ( ); - } else { - joinButton = ( - - ); } return ( - - - -
    - {user.username} -
    -
    - {details} -
    - - - {joinButton} - - + ); } 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( - - - ); - } - - let memberString = formatMessage(holders.member); - if (users.length !== 1) { - memberString += 's'; - } - - let count; - if (users.length === this.state.users.length) { - count = ( - - ); - } else { - count = ( - - ); + let maxHeight = 1000; + if (Utils.windowHeight() <= 1200) { + maxHeight = Utils.windowHeight() - 300; } return ( @@ -270,30 +123,12 @@ class MoreDirectChannels extends React.Component { /> - -
    -
    - -
    -
    - {count} -
    -
    -
    - - - {userEntries} - -
    -
    + +