chinwagsocial/app/javascript/mastodon/features/list_timeline/index.jsx

233 lines
7.8 KiB
React
Raw Permalink Normal View History

2017-11-25 10:35:37 +11:00
import PropTypes from 'prop-types';
2023-05-23 18:52:27 +10:00
import { PureComponent } from 'react';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { Helmet } from 'react-helmet';
import { withRouter } from 'react-router-dom';
2017-11-25 10:35:37 +11:00
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import Toggle from 'react-toggle';
import DeleteIcon from '@/material-icons/400-24px/delete.svg?react';
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
import { fetchList, updateList } from 'mastodon/actions/lists';
import { openModal } from 'mastodon/actions/modal';
import { connectListStream } from 'mastodon/actions/streaming';
import { expandListTimeline } from 'mastodon/actions/timelines';
import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import { Icon } from 'mastodon/components/icon';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { RadioButton } from 'mastodon/components/radio_button';
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
import StatusListContainer from 'mastodon/features/ui/containers/status_list_container';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
const messages = defineMessages({
followed: { id: 'lists.replies_policy.followed', defaultMessage: 'Any followed user' },
none: { id: 'lists.replies_policy.none', defaultMessage: 'No one' },
list: { id: 'lists.replies_policy.list', defaultMessage: 'Members of the list' },
});
2017-11-25 10:35:37 +11:00
const mapStateToProps = (state, props) => ({
list: state.getIn(['lists', props.params.id]),
hasUnread: state.getIn(['timelines', `list:${props.params.id}`, 'unread']) > 0,
});
2023-05-23 18:52:27 +10:00
class ListTimeline extends PureComponent {
2017-11-25 10:35:37 +11:00
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,
...WithRouterPropTypes,
2017-11-25 10:35:37 +11:00
};
handlePin = () => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(removeColumn(columnId));
} else {
dispatch(addColumn('LIST', { id: this.props.params.id }));
this.props.history.push('/');
2017-11-25 10:35:37 +11:00
}
2023-01-30 11:45:35 +11:00
};
2017-11-25 10:35:37 +11:00
handleMove = (dir) => {
const { columnId, dispatch } = this.props;
dispatch(moveColumn(columnId, dir));
2023-01-30 11:45:35 +11:00
};
2017-11-25 10:35:37 +11:00
handleHeaderClick = () => {
this.column.scrollTop();
2023-01-30 11:45:35 +11:00
};
2017-11-25 10:35:37 +11:00
componentDidMount () {
const { dispatch } = this.props;
const { id } = this.props.params;
dispatch(fetchList(id));
dispatch(expandListTimeline(id));
2017-11-25 10:35:37 +11:00
this.disconnect = dispatch(connectListStream(id));
}
UNSAFE_componentWillReceiveProps (nextProps) {
const { dispatch } = this.props;
const { id } = nextProps.params;
if (id !== this.props.params.id) {
if (this.disconnect) {
this.disconnect();
this.disconnect = null;
}
dispatch(fetchList(id));
dispatch(expandListTimeline(id));
this.disconnect = dispatch(connectListStream(id));
}
}
2017-11-25 10:35:37 +11:00
componentWillUnmount () {
if (this.disconnect) {
this.disconnect();
this.disconnect = null;
}
}
setRef = c => {
this.column = c;
2023-01-30 11:45:35 +11:00
};
2017-11-25 10:35:37 +11:00
handleLoadMore = maxId => {
2017-11-25 10:35:37 +11:00
const { id } = this.props.params;
this.props.dispatch(expandListTimeline(id, { maxId }));
2023-01-30 11:45:35 +11:00
};
2017-11-25 10:35:37 +11:00
handleEditClick = () => {
this.props.dispatch(openModal({
modalType: 'LIST_EDITOR',
modalProps: { listId: this.props.params.id },
}));
2023-01-30 11:45:35 +11:00
};
handleDeleteClick = () => {
const { dispatch, columnId } = this.props;
const { id } = this.props.params;
dispatch(openModal({ modalType: 'CONFIRM_DELETE_LIST', modalProps: { listId: id, columnId } }));
2023-01-30 11:45:35 +11:00
};
handleRepliesPolicyChange = ({ target }) => {
const { dispatch } = this.props;
const { id } = this.props.params;
dispatch(updateList(id, undefined, false, undefined, target.value));
};
onExclusiveToggle = ({ target }) => {
const { dispatch } = this.props;
const { id } = this.props.params;
dispatch(updateList(id, undefined, false, target.checked, undefined));
2023-01-30 11:45:35 +11:00
};
2017-11-25 10:35:37 +11:00
render () {
const { hasUnread, columnId, multiColumn, list, intl } = this.props;
2017-11-25 10:35:37 +11:00
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 isExclusive = list ? list.get('exclusive') : undefined;
2017-11-25 10:35:37 +11:00
if (typeof list === 'undefined') {
return (
<Column>
<div className='scrollable'>
<LoadingIndicator />
</div>
</Column>
);
} else if (list === false) {
return (
<BundleColumnError multiColumn={multiColumn} errorType='routing' />
);
}
2017-11-25 10:35:37 +11:00
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={title}>
2017-11-25 10:35:37 +11:00
<ColumnHeader
icon='list-ul'
iconComponent={ListAltIcon}
2017-11-25 10:35:37 +11:00
active={hasUnread}
title={title}
onPin={this.handlePin}
onMove={this.handleMove}
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
>
<div className='column-settings'>
<section className='column-header__links'>
<button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleEditClick}>
<Icon id='pencil' icon={EditIcon} /> <FormattedMessage id='lists.edit' defaultMessage='Edit list' />
</button>
<button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleDeleteClick}>
<Icon id='trash' icon={DeleteIcon} /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' />
</button>
</section>
<section>
<div className='setting-toggle'>
<Toggle id={`list-${id}-exclusive`} checked={isExclusive} onChange={this.onExclusiveToggle} />
<label htmlFor={`list-${id}-exclusive`} className='setting-toggle__label'>
<FormattedMessage id='lists.exclusive' defaultMessage='Hide these posts from home' />
</label>
</div>
</section>
{replies_policy !== undefined && (
<section aria-labelledby={`list-${id}-replies-policy`}>
<h3 id={`list-${id}-replies-policy`}><FormattedMessage id='lists.replies_policy.title' defaultMessage='Show replies to:' /></h3>
<div className='column-settings__row'>
{ ['none', 'list', 'followed'].map(policy => (
<RadioButton name='order' key={policy} value={policy} label={intl.formatMessage(messages[policy])} checked={replies_policy === policy} onChange={this.handleRepliesPolicyChange} />
))}
</div>
</section>
)}
</div>
</ColumnHeader>
2017-11-25 10:35:37 +11:00
<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. When members of this list post new statuses, they will appear here.' />}
bindToDocument={!multiColumn}
2017-11-25 10:35:37 +11:00
/>
<Helmet>
<title>{title}</title>
<meta name='robots' content='noindex' />
</Helmet>
2017-11-25 10:35:37 +11:00
</Column>
);
}
}
export default withRouter(connect(mapStateToProps)(injectIntl(ListTimeline)));