forked from treehouse/mastodon
Display list column (#5750)
parent
269a445c0b
commit
31ac5f0e00
|
@ -0,0 +1,28 @@
|
|||
import api from '../api';
|
||||
|
||||
export const LIST_FETCH_REQUEST = 'LIST_FETCH_REQUEST';
|
||||
export const LIST_FETCH_SUCCESS = 'LIST_FETCH_SUCCESS';
|
||||
export const LIST_FETCH_FAIL = 'LIST_FETCH_FAIL';
|
||||
|
||||
export const fetchList = id => (dispatch, getState) => {
|
||||
dispatch(fetchListRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/lists/${id}`)
|
||||
.then(({ data }) => dispatch(fetchListSuccess(data)))
|
||||
.catch(err => dispatch(fetchListFail(err)));
|
||||
};
|
||||
|
||||
export const fetchListRequest = id => ({
|
||||
type: LIST_FETCH_REQUEST,
|
||||
id,
|
||||
});
|
||||
|
||||
export const fetchListSuccess = list => ({
|
||||
type: LIST_FETCH_SUCCESS,
|
||||
list,
|
||||
});
|
||||
|
||||
export const fetchListFail = error => ({
|
||||
type: LIST_FETCH_FAIL,
|
||||
error,
|
||||
});
|
|
@ -51,3 +51,4 @@ export const connectCommunityStream = () => connectTimelineStream('community', '
|
|||
export const connectMediaStream = () => connectTimelineStream('community', 'public:local');
|
||||
export const connectPublicStream = () => connectTimelineStream('public', 'public');
|
||||
export const connectHashtagStream = (tag) => connectTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`);
|
||||
export const connectListStream = (id) => connectTimelineStream(`list:${id}`, `list&list=${id}`);
|
||||
|
|
|
@ -118,6 +118,7 @@ export const refreshCommunityTimeline = () => refreshTimeline('community', '/
|
|||
export const refreshAccountTimeline = accountId => refreshTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
|
||||
export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
|
||||
export const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
|
||||
export const refreshListTimeline = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`);
|
||||
|
||||
export function refreshTimelineFail(timeline, error, skipLoading) {
|
||||
return {
|
||||
|
@ -158,6 +159,7 @@ export const expandCommunityTimeline = () => expandTimeline('community', '/ap
|
|||
export const expandAccountTimeline = accountId => expandTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
|
||||
export const expandAccountMediaTimeline = accountId => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
|
||||
export const expandHashtagTimeline = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
|
||||
export const expandListTimeline = id => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`);
|
||||
|
||||
export function expandTimelineRequest(timeline) {
|
||||
return {
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import StatusListContainer from '../ui/containers/status_list_container';
|
||||
import Column from '../../components/column';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connectListStream } from '../../actions/streaming';
|
||||
import { refreshListTimeline, expandListTimeline } from '../../actions/timelines';
|
||||
import { fetchList } from '../../actions/lists';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
list: state.getIn(['lists', props.params.id]),
|
||||
hasUnread: state.getIn(['timelines', `list:${props.params.id}`, 'unread']) > 0,
|
||||
});
|
||||
|
||||
@connect(mapStateToProps)
|
||||
export default class ListTimeline extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
columnId: PropTypes.string,
|
||||
hasUnread: PropTypes.bool,
|
||||
multiColumn: PropTypes.bool,
|
||||
list: ImmutablePropTypes.map,
|
||||
};
|
||||
|
||||
handlePin = () => {
|
||||
const { columnId, dispatch } = this.props;
|
||||
|
||||
if (columnId) {
|
||||
dispatch(removeColumn(columnId));
|
||||
} else {
|
||||
dispatch(addColumn('LIST', { id: this.props.params.id }));
|
||||
}
|
||||
}
|
||||
|
||||
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(refreshListTimeline(id));
|
||||
|
||||
this.disconnect = dispatch(connectListStream(id));
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.disconnect) {
|
||||
this.disconnect();
|
||||
this.disconnect = null;
|
||||
}
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.column = c;
|
||||
}
|
||||
|
||||
handleLoadMore = () => {
|
||||
const { id } = this.props.params;
|
||||
this.props.dispatch(expandListTimeline(id));
|
||||
}
|
||||
|
||||
render () {
|
||||
const { hasUnread, columnId, multiColumn, list } = this.props;
|
||||
const { id } = this.props.params;
|
||||
const pinned = !!columnId;
|
||||
const title = list ? list.get('title') : id;
|
||||
|
||||
return (
|
||||
<Column ref={this.setRef}>
|
||||
<ColumnHeader
|
||||
icon='bars'
|
||||
active={hasUnread}
|
||||
title={title}
|
||||
onPin={this.handlePin}
|
||||
onMove={this.handleMove}
|
||||
onClick={this.handleHeaderClick}
|
||||
pinned={pinned}
|
||||
multiColumn={multiColumn}
|
||||
/>
|
||||
|
||||
<StatusListContainer
|
||||
trackScroll={!pinned}
|
||||
scrollKey={`list_timeline-${columnId}`}
|
||||
timelineId={`list:${id}`}
|
||||
loadMore={this.handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet.' />}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -11,7 +11,7 @@ import BundleContainer from '../containers/bundle_container';
|
|||
import ColumnLoading from './column_loading';
|
||||
import DrawerLoading from './drawer_loading';
|
||||
import BundleColumnError from './bundle_column_error';
|
||||
import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, FavouritedStatuses } from '../../ui/util/async-components';
|
||||
import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, FavouritedStatuses, ListTimeline } from '../../ui/util/async-components';
|
||||
|
||||
import detectPassiveEvents from 'detect-passive-events';
|
||||
import { scrollRight } from '../../../scroll';
|
||||
|
@ -24,6 +24,7 @@ const componentMap = {
|
|||
'COMMUNITY': CommunityTimeline,
|
||||
'HASHTAG': HashtagTimeline,
|
||||
'FAVOURITES': FavouritedStatuses,
|
||||
'LIST': ListTimeline,
|
||||
};
|
||||
|
||||
@component => injectIntl(component, { withRef: true })
|
||||
|
|
|
@ -33,6 +33,7 @@ import {
|
|||
FollowRequests,
|
||||
GenericNotFound,
|
||||
FavouritedStatuses,
|
||||
ListTimeline,
|
||||
Blocks,
|
||||
Mutes,
|
||||
PinnedStatuses,
|
||||
|
@ -372,6 +373,7 @@ export default class UI extends React.Component {
|
|||
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
|
||||
|
||||
<WrappedRoute path='/notifications' component={Notifications} content={children} />
|
||||
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
|
||||
|
|
|
@ -26,6 +26,10 @@ export function HashtagTimeline () {
|
|||
return import(/* webpackChunkName: "features/hashtag_timeline" */'../../hashtag_timeline');
|
||||
}
|
||||
|
||||
export function ListTimeline () {
|
||||
return import(/* webpackChunkName: "features/list_timeline" */'../../list_timeline');
|
||||
}
|
||||
|
||||
export function Status () {
|
||||
return import(/* webpackChunkName: "features/status" */'../../status');
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import media_attachments from './media_attachments';
|
|||
import notifications from './notifications';
|
||||
import height_cache from './height_cache';
|
||||
import custom_emojis from './custom_emojis';
|
||||
import lists from './lists';
|
||||
|
||||
const reducers = {
|
||||
timelines,
|
||||
|
@ -47,6 +48,7 @@ const reducers = {
|
|||
notifications,
|
||||
height_cache,
|
||||
custom_emojis,
|
||||
lists,
|
||||
};
|
||||
|
||||
export default combineReducers(reducers);
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import { LIST_FETCH_SUCCESS } from '../actions/lists';
|
||||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||
|
||||
const initialState = ImmutableMap();
|
||||
|
||||
const normalizeList = (state, list) => state.set(list.id, fromJS(list));
|
||||
|
||||
export default function lists(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case LIST_FETCH_SUCCESS:
|
||||
return normalizeList(state, action.list);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
|
@ -2,4 +2,8 @@
|
|||
|
||||
class REST::ListSerializer < ActiveModel::Serializer
|
||||
attributes :id, :title
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue