mastodon/app/javascript/flavours/glitch/features/list_timeline/index.js

223 lines
7.8 KiB
JavaScript

import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
import Column from 'flavours/glitch/components/column';
import ColumnHeader from 'flavours/glitch/components/column_header';
import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { connectListStream } from 'flavours/glitch/actions/streaming';
import { expandListTimeline } from 'flavours/glitch/actions/timelines';
import { fetchList, deleteList, updateList } from 'flavours/glitch/actions/lists';
import { openModal } from 'flavours/glitch/actions/modal';
import MissingIndicator from 'flavours/glitch/components/missing_indicator';
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
import Toggle from 'react-toggle';
const messages = defineMessages({
deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' },
deleteConfirm: { id: 'confirmations.delete_list.confirm', defaultMessage: 'Delete' },
show_self: { id: 'lists.show_self', defaultMessage: 'Include your own toots' },
all_replies: { id: 'lists.replies_policy.all_replies', defaultMessage: 'any followed user' },
no_replies: { id: 'lists.replies_policy.no_replies', defaultMessage: 'no one' },
list_replies: { id: 'lists.replies_policy.list_replies', defaultMessage: 'members of the list' },
});
const mapStateToProps = (state, props) => ({
list: state.getIn(['lists', props.params.id]),
hasUnread: state.getIn(['timelines', `list:${props.params.id}`, 'unread']) > 0,
});
@connect(mapStateToProps)
@injectIntl
export default class ListTimeline extends React.PureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
columnId: PropTypes.string,
hasUnread: PropTypes.bool,
multiColumn: PropTypes.bool,
list: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
intl: PropTypes.object.isRequired,
};
handlePin = () => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(removeColumn(columnId));
} else {
dispatch(addColumn('LIST', { id: this.props.params.id }));
this.context.router.history.push('/');
}
}
handleMove = (dir) => {
const { columnId, dispatch } = this.props;
dispatch(moveColumn(columnId, dir));
}
handleHeaderClick = () => {
this.column.scrollTop();
}
componentDidMount () {
const { dispatch } = this.props;
const { id } = this.props.params;
dispatch(fetchList(id));
dispatch(expandListTimeline(id));
this.disconnect = dispatch(connectListStream(id));
}
componentWillUnmount () {
if (this.disconnect) {
this.disconnect();
this.disconnect = null;
}
}
setRef = c => {
this.column = c;
}
handleLoadMore = maxId => {
const { id } = this.props.params;
this.props.dispatch(expandListTimeline(id, { maxId }));
}
handleEditClick = () => {
this.props.dispatch(openModal('LIST_EDITOR', { listId: this.props.params.id }));
}
handleDeleteClick = () => {
const { dispatch, columnId, intl } = this.props;
const { id } = this.props.params;
dispatch(openModal('CONFIRM', {
message: intl.formatMessage(messages.deleteMessage),
confirm: intl.formatMessage(messages.deleteConfirm),
onConfirm: () => {
dispatch(deleteList(id));
if (!!columnId) {
dispatch(removeColumn(columnId));
} else {
this.context.router.history.push('/lists');
}
},
}));
}
handleShowSelfChange = ({ target }) => {
const { dispatch, list } = this.props;
const { id } = this.props.params;
const replies_policy = list ? list.get('replies_policy') : undefined;
const show_self = list ? list.get('show_self') : false;
this.props.dispatch(updateList(id, undefined, false, replies_policy, !show_self));
}
handleRepliesPolicyChange = ({ target }) => {
const { dispatch, list } = this.props;
const { id } = this.props.params;
this.props.dispatch(updateList(id, undefined, false, target.value));
}
render () {
const { hasUnread, columnId, multiColumn, list, intl } = this.props;
const { id } = this.props.params;
const pinned = !!columnId;
const title = list ? list.get('title') : id;
const replies_policy = list ? list.get('replies_policy') : undefined;
const show_self = list ? list.get('show_self') : false;
if (typeof list === 'undefined') {
return (
<Column>
<div className='scrollable'>
<LoadingIndicator />
</div>
</Column>
);
} else if (list === false) {
return (
<Column>
<div className='scrollable'>
<MissingIndicator />
</div>
</Column>
);
}
return (
<Column ref={this.setRef} label={title}>
<ColumnHeader
icon='list-ul'
active={hasUnread}
title={title}
onPin={this.handlePin}
onMove={this.handleMove}
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
>
<div className='column-header__links'>
<button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleEditClick}>
<i className='fa fa-pencil' /> <FormattedMessage id='lists.edit' defaultMessage='Edit list' />
</button>
<button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleDeleteClick}>
<i className='fa fa-trash' /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' />
</button>
</div>
<div className='column-settings__row'>
<div className='setting-toggle'>
<Toggle id={['setting', 'toggle', id, 'show_self'].join('-')} checked={show_self === true} onChange={this.handleShowSelfChange} />
<label htmlFor={['setting', 'toggle', id, 'show_self'].join('-')} className='setting-toggle__label'>
<FormattedMessage id='lists.show_self' defaultMessage='Include your own toots' />
</label>
</div>
</div>
{ replies_policy !== undefined && (
<div>
<div className='column-settings__row'>
<fieldset>
<legend><FormattedMessage id='lists.replies_policy.title' defaultMessage='Show replies to:' /></legend>
{ ['no_replies', 'list_replies', 'all_replies'].map(policy => (
<div key={['setting', 'radio', id, policy].join('-')} className='setting-radio'>
<input className='setting-radio__input' id={['setting', 'radio', id, policy].join('-')} type='radio' value={policy} checked={replies_policy === policy} onChange={this.handleRepliesPolicyChange} />
<label className='setting-radio__label' htmlFor={['setting', 'radio', id, policy].join('-')}>
<FormattedMessage {...messages[policy]} />
</label>
</div>
))}
</fieldset>
</div>
</div>
)}
<hr />
</ColumnHeader>
<StatusListContainer
trackScroll={!pinned}
scrollKey={`list_timeline-${columnId}`}
timelineId={`list:${id}`}
onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet.' />}
/>
</Column>
);
}
}