PLT-3455 More channels filter (#4022)
Этот коммит содержится в:
коммит произвёл
Joram Wilander
родитель
b180bb46e3
Коммит
2659d19d05
180
webapp/components/filtered_channel_list.jsx
Обычный файл
180
webapp/components/filtered_channel_list.jsx
Обычный файл
@@ -0,0 +1,180 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import $ from 'jquery';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import * as UserAgent from 'utils/user_agent.jsx';
|
||||||
|
|
||||||
|
import {localizeMessage} from 'utils/utils.jsx';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import loadingGif from 'images/load.gif';
|
||||||
|
|
||||||
|
export default class FilteredChannelList extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleFilterChange = this.handleFilterChange.bind(this);
|
||||||
|
this.createChannelRow = this.createChannelRow.bind(this);
|
||||||
|
this.filterChannels = this.filterChannels.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
filter: '',
|
||||||
|
joiningChannel: '',
|
||||||
|
channels: this.filterChannels(props.channels)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
// assume the channel list is immutable
|
||||||
|
if (this.props.channels !== nextProps.channels) {
|
||||||
|
this.setState({
|
||||||
|
channels: this.filterChannels(nextProps.channels)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
// only focus the search box on desktop so that we don't cause the keyboard to open on mobile
|
||||||
|
if (!UserAgent.isMobileApp()) {
|
||||||
|
ReactDOM.findDOMNode(this.refs.filter).focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
if (prevState.filter !== this.state.filter) {
|
||||||
|
$(ReactDOM.findDOMNode(this.refs.channelList)).scrollTop(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleJoin(channel) {
|
||||||
|
this.setState({joiningChannel: channel.id});
|
||||||
|
this.props.handleJoin(
|
||||||
|
channel,
|
||||||
|
() => {
|
||||||
|
this.setState({joiningChannel: ''});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createChannelRow(channel) {
|
||||||
|
let joinButton;
|
||||||
|
if (this.state.joiningChannel === channel.id) {
|
||||||
|
joinButton = (
|
||||||
|
<img
|
||||||
|
className='join-channel-loading-gif'
|
||||||
|
src={loadingGif}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
joinButton = (
|
||||||
|
<button
|
||||||
|
onClick={this.handleJoin.bind(this, channel)}
|
||||||
|
className='btn btn-primary'
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='more_channels.join'
|
||||||
|
defaultMessage='Join'
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className='more-modal__row'
|
||||||
|
key={channel.id}
|
||||||
|
>
|
||||||
|
<div className='more-modal__details'>
|
||||||
|
<p className='more-modal__name'>{channel.display_name}</p>
|
||||||
|
<p className='more-modal__description'>{channel.purpose}</p>
|
||||||
|
</div>
|
||||||
|
<div className='more-modal__actions'>
|
||||||
|
{joinButton}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
filterChannels(channels) {
|
||||||
|
if (!this.state || !this.state.filter) {
|
||||||
|
return channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
return channels.filter((chan) => {
|
||||||
|
const filter = this.state.filter.toLowerCase();
|
||||||
|
return !!((chan.name.toLowerCase().indexOf(filter) !== -1 || chan.display_name.toLowerCase().indexOf(filter) !== -1) && chan.delete_at === 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFilterChange(e) {
|
||||||
|
this.setState({
|
||||||
|
filter: e.target.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let channels = this.state.channels;
|
||||||
|
|
||||||
|
if (this.state.filter && this.state.filter.length > 0) {
|
||||||
|
channels = this.filterChannels(channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
let count;
|
||||||
|
if (channels.length === this.props.channels.length) {
|
||||||
|
count = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='filtered_channels_list.count'
|
||||||
|
defaultMessage='{count} {count, plural, =0 {0 channels} one {channel} other {channels}}'
|
||||||
|
values={{
|
||||||
|
count: channels.length
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
count = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='filtered_channels_list.countTotal'
|
||||||
|
defaultMessage='{count} {count, plural, =0 {0 channels} one {channel} other {channels}} of {total} Total'
|
||||||
|
values={{
|
||||||
|
count: channels.length,
|
||||||
|
total: this.props.channels.length
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='filtered-user-list'>
|
||||||
|
<div className='filter-row'>
|
||||||
|
<div className='col-sm-6'>
|
||||||
|
<input
|
||||||
|
ref='filter'
|
||||||
|
className='form-control filter-textbox'
|
||||||
|
placeholder={localizeMessage('filtered_channels_list.search', 'Search channels')}
|
||||||
|
onInput={this.handleFilterChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='col-sm-12'>
|
||||||
|
<span className='channel-count pull-left'>{count}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref='channelList'
|
||||||
|
className='more-modal__list'
|
||||||
|
>
|
||||||
|
{channels.map(this.createChannelRow)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FilteredChannelList.defaultProps = {
|
||||||
|
channels: []
|
||||||
|
};
|
||||||
|
|
||||||
|
FilteredChannelList.propTypes = {
|
||||||
|
channels: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||||
|
handleJoin: React.PropTypes.func.isRequired
|
||||||
|
};
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import LoadingScreen from './loading_screen.jsx';
|
import LoadingScreen from './loading_screen.jsx';
|
||||||
import NewChannelFlow from './new_channel_flow.jsx';
|
import NewChannelFlow from './new_channel_flow.jsx';
|
||||||
|
import FilteredChannelList from './filtered_channel_list.jsx';
|
||||||
|
|
||||||
import ChannelStore from 'stores/channel_store.jsx';
|
import ChannelStore from 'stores/channel_store.jsx';
|
||||||
import UserStore from 'stores/user_store.jsx';
|
import UserStore from 'stores/user_store.jsx';
|
||||||
@@ -18,18 +19,8 @@ import {FormattedMessage} from 'react-intl';
|
|||||||
import {browserHistory} from 'react-router/es6';
|
import {browserHistory} from 'react-router/es6';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||||
|
|
||||||
import loadingGif from 'images/load.gif';
|
|
||||||
|
|
||||||
function getStateFromStores() {
|
|
||||||
return {
|
|
||||||
channels: ChannelStore.getMoreAll(),
|
|
||||||
serverError: null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class MoreChannels extends React.Component {
|
export default class MoreChannels extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -37,100 +28,78 @@ export default class MoreChannels extends React.Component {
|
|||||||
this.onListenerChange = this.onListenerChange.bind(this);
|
this.onListenerChange = this.onListenerChange.bind(this);
|
||||||
this.handleJoin = this.handleJoin.bind(this);
|
this.handleJoin = this.handleJoin.bind(this);
|
||||||
this.handleNewChannel = this.handleNewChannel.bind(this);
|
this.handleNewChannel = this.handleNewChannel.bind(this);
|
||||||
this.createChannelRow = this.createChannelRow.bind(this);
|
|
||||||
|
|
||||||
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
|
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
|
||||||
|
|
||||||
var initState = getStateFromStores();
|
const initState = this.getStateFromStores();
|
||||||
initState.channelType = '';
|
initState.channelType = '';
|
||||||
initState.joiningChannel = '';
|
|
||||||
initState.showNewChannelModal = false;
|
initState.showNewChannelModal = false;
|
||||||
this.state = initState;
|
this.state = initState;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
const self = this;
|
||||||
ChannelStore.addMoreChangeListener(this.onListenerChange);
|
ChannelStore.addMoreChangeListener(this.onListenerChange);
|
||||||
$(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', () => {
|
|
||||||
|
$(this.refs.modal).on('shown.bs.modal', () => {
|
||||||
AsyncClient.getMoreChannels(true);
|
AsyncClient.getMoreChannels(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
var self = this;
|
$(this.refs.modal).on('show.bs.modal', (e) => {
|
||||||
$(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', (e) => {
|
const button = e.relatedTarget;
|
||||||
var button = e.relatedTarget;
|
|
||||||
self.setState({channelType: $(button).attr('data-channeltype')});
|
self.setState({channelType: $(button).attr('data-channeltype')});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
ChannelStore.removeMoreChangeListener(this.onListenerChange);
|
ChannelStore.removeMoreChangeListener(this.onListenerChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getStateFromStores() {
|
||||||
|
return {
|
||||||
|
channels: ChannelStore.getMoreAll(),
|
||||||
|
serverError: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
onListenerChange() {
|
onListenerChange() {
|
||||||
var newState = getStateFromStores();
|
const newState = this.getStateFromStores();
|
||||||
if (!Utils.areObjectsEqual(newState.channels, this.state.channels)) {
|
if (!Utils.areObjectsEqual(newState.channels, this.state.channels)) {
|
||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleJoin(channel) {
|
|
||||||
this.setState({joiningChannel: channel.id});
|
handleJoin(channel, done) {
|
||||||
GlobalActions.emitJoinChannelEvent(
|
GlobalActions.emitJoinChannelEvent(
|
||||||
channel,
|
channel,
|
||||||
() => {
|
() => {
|
||||||
$(ReactDOM.findDOMNode(this.refs.modal)).modal('hide');
|
$(this.refs.modal).modal('hide');
|
||||||
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name);
|
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name);
|
||||||
this.setState({joiningChannel: ''});
|
if (done) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
this.setState({joiningChannel: '', serverError: err.message});
|
this.setState({serverError: err.message});
|
||||||
|
if (done) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNewChannel() {
|
handleNewChannel() {
|
||||||
$(ReactDOM.findDOMNode(this.refs.modal)).modal('hide');
|
$(this.refs.modal).modal('hide');
|
||||||
this.setState({showNewChannelModal: true});
|
this.setState({showNewChannelModal: true});
|
||||||
}
|
}
|
||||||
createChannelRow(channel) {
|
|
||||||
let joinButton;
|
|
||||||
if (this.state.joiningChannel === channel.id) {
|
|
||||||
joinButton = (
|
|
||||||
<img
|
|
||||||
className='join-channel-loading-gif'
|
|
||||||
src={loadingGif}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
joinButton = (
|
|
||||||
<button
|
|
||||||
onClick={this.handleJoin.bind(self, channel)}
|
|
||||||
className='btn btn-primary'
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='more_channels.join'
|
|
||||||
defaultMessage='Join'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className='more-modal__row'
|
|
||||||
key={channel.id}
|
|
||||||
>
|
|
||||||
<div className='more-modal__details'>
|
|
||||||
<p className='more-modal__name'>{channel.display_name}</p>
|
|
||||||
<p className='more-modal__description'>{channel.purpose}</p>
|
|
||||||
</div>
|
|
||||||
<div className='more-modal__actions'>
|
|
||||||
{joinButton}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
render() {
|
render() {
|
||||||
let maxHeight = 1000;
|
let maxHeight = 1000;
|
||||||
if (Utils.windowHeight() <= 1200) {
|
if (Utils.windowHeight() <= 1200) {
|
||||||
maxHeight = Utils.windowHeight() - 300;
|
maxHeight = Utils.windowHeight() - 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
var serverError;
|
let serverError;
|
||||||
if (this.state.serverError) {
|
if (this.state.serverError) {
|
||||||
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
|
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
|
||||||
}
|
}
|
||||||
@@ -170,7 +139,7 @@ export default class MoreChannels extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var moreChannels;
|
let moreChannels;
|
||||||
|
|
||||||
if (this.state.channels != null) {
|
if (this.state.channels != null) {
|
||||||
var channels = this.state.channels;
|
var channels = this.state.channels;
|
||||||
@@ -178,9 +147,10 @@ export default class MoreChannels extends React.Component {
|
|||||||
moreChannels = <LoadingScreen/>;
|
moreChannels = <LoadingScreen/>;
|
||||||
} else if (channels.length) {
|
} else if (channels.length) {
|
||||||
moreChannels = (
|
moreChannels = (
|
||||||
<div className='more-modal__list'>
|
<FilteredChannelList
|
||||||
{channels.map(this.createChannelRow)}
|
channels={channels}
|
||||||
</div>
|
handleJoin={this.handleJoin}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
moreChannels = (
|
moreChannels = (
|
||||||
|
|||||||
@@ -1182,6 +1182,9 @@
|
|||||||
"file_upload.filesAbove": "Files above {max}MB cannot be uploaded: {filenames}",
|
"file_upload.filesAbove": "Files above {max}MB cannot be uploaded: {filenames}",
|
||||||
"file_upload.limited": "Uploads limited to {count} files maximum. Please use additional posts for more files.",
|
"file_upload.limited": "Uploads limited to {count} files maximum. Please use additional posts for more files.",
|
||||||
"file_upload.pasted": "Image Pasted at ",
|
"file_upload.pasted": "Image Pasted at ",
|
||||||
|
"filtered_channels_list.count": "{count} {count, plural, =0 {0 channels} one {channel} other {channels}}",
|
||||||
|
"filtered_channels_list.countTotal": "{count} {count, plural, =0 {0 channels} one {channel} other {channels}} of {total} Total",
|
||||||
|
"filtered_channels_list.search": "Search channels",
|
||||||
"filtered_user_list.any_team": "All Users",
|
"filtered_user_list.any_team": "All Users",
|
||||||
"filtered_user_list.count": "{count} {count, plural, =0 {0 members} one {member} other {members}}",
|
"filtered_user_list.count": "{count} {count, plural, =0 {0 members} one {member} other {members}}",
|
||||||
"filtered_user_list.countTotal": "{count} {count, plural, =0 {0 members} one {member} other {members}} of {total} Total",
|
"filtered_user_list.countTotal": "{count} {count, plural, =0 {0 members} one {member} other {members}} of {total} Total",
|
||||||
|
|||||||
@@ -227,7 +227,14 @@
|
|||||||
|
|
||||||
&.more-channel__modal {
|
&.more-channel__modal {
|
||||||
.modal-body {
|
.modal-body {
|
||||||
padding: 0;
|
overflow-x: hidden;
|
||||||
|
padding: 10px 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-count {
|
||||||
|
@include opacity(.8);
|
||||||
|
float: right;
|
||||||
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -307,7 +307,8 @@
|
|||||||
max-width: calc(100% - 10px);
|
max-width: calc(100% - 10px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-direct-channels {
|
.modal-direct-channels,
|
||||||
|
.more-channel__modal {
|
||||||
.member-count {
|
.member-count {
|
||||||
display: block;
|
display: block;
|
||||||
float: none;
|
float: none;
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user