* Added a timeline for Direct statuses * Lists all Direct statuses you've sent and received * Displayed in Getting Started * Streaming server support for direct TL * Changes to match other timelines in 2.0pull/4/head
@ -0,0 +1,60 @@ | |||
# frozen_string_literal: true | |||
class Api::V1::Timelines::DirectController < Api::BaseController | |||
before_action -> { doorkeeper_authorize! :read }, only: [:show] | |||
before_action :require_user!, only: [:show] | |||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? } | |||
respond_to :json | |||
def show | |||
@statuses = load_statuses | |||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) | |||
end | |||
private | |||
def load_statuses | |||
cached_direct_statuses | |||
end | |||
def cached_direct_statuses | |||
cache_collection direct_statuses, Status | |||
end | |||
def direct_statuses | |||
direct_timeline_statuses.paginate_by_max_id( | |||
limit_param(DEFAULT_STATUSES_LIMIT), | |||
params[:max_id], | |||
params[:since_id] | |||
) | |||
end | |||
def direct_timeline_statuses | |||
Status.as_direct_timeline(current_account) | |||
end | |||
def insert_pagination_headers | |||
set_pagination_headers(next_path, prev_path) | |||
end | |||
def pagination_params(core_params) | |||
params.permit(:local, :limit).merge(core_params) | |||
end | |||
def next_path | |||
api_v1_timelines_direct_url pagination_params(max_id: pagination_max_id) | |||
end | |||
def prev_path | |||
api_v1_timelines_direct_url pagination_params(since_id: pagination_since_id) | |||
end | |||
def pagination_max_id | |||
@statuses.last.id | |||
end | |||
def pagination_since_id | |||
@statuses.first.id | |||
end | |||
end |
@ -0,0 +1,17 @@ | |||
import { connect } from 'react-redux'; | |||
import ColumnSettings from '../../community_timeline/components/column_settings'; | |||
import { changeSetting } from '../../../actions/settings'; | |||
const mapStateToProps = state => ({ | |||
settings: state.getIn(['settings', 'direct']), | |||
}); | |||
const mapDispatchToProps = dispatch => ({ | |||
onChange (key, checked) { | |||
dispatch(changeSetting(['direct', ...key], checked)); | |||
}, | |||
}); | |||
export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings); |
@ -0,0 +1,104 @@ | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import PropTypes from 'prop-types'; | |||
import StatusListContainer from '../ui/containers/status_list_container'; | |||
import Column from '../../components/column'; | |||
import ColumnHeader from '../../components/column_header'; | |||
import { expandDirectTimeline } from '../../actions/timelines'; | |||
import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; | |||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | |||
import ColumnSettingsContainer from './containers/column_settings_container'; | |||
import { connectDirectStream } from '../../actions/streaming'; | |||
const messages = defineMessages({ | |||
title: { id: 'column.direct', defaultMessage: 'Direct messages' }, | |||
}); | |||
const mapStateToProps = state => ({ | |||
hasUnread: state.getIn(['timelines', 'direct', 'unread']) > 0, | |||
}); | |||
@connect(mapStateToProps) | |||
@injectIntl | |||
export default class DirectTimeline extends React.PureComponent { | |||
static propTypes = { | |||
dispatch: PropTypes.func.isRequired, | |||
columnId: PropTypes.string, | |||
intl: PropTypes.object.isRequired, | |||
hasUnread: PropTypes.bool, | |||
multiColumn: PropTypes.bool, | |||
}; | |||
handlePin = () => { | |||
const { columnId, dispatch } = this.props; | |||
if (columnId) { | |||
dispatch(removeColumn(columnId)); | |||
} else { | |||
dispatch(addColumn('DIRECT', {})); | |||
} | |||
} | |||
handleMove = (dir) => { | |||
const { columnId, dispatch } = this.props; | |||
dispatch(moveColumn(columnId, dir)); | |||
} | |||
handleHeaderClick = () => { | |||
this.column.scrollTop(); | |||
} | |||
componentDidMount () { | |||
const { dispatch } = this.props; | |||
dispatch(expandDirectTimeline()); | |||
this.disconnect = dispatch(connectDirectStream()); | |||
} | |||
componentWillUnmount () { | |||
if (this.disconnect) { | |||
this.disconnect(); | |||
this.disconnect = null; | |||
} | |||
} | |||
setRef = c => { | |||
this.column = c; | |||
} | |||
handleLoadMore = maxId => { | |||
this.props.dispatch(expandDirectTimeline({ maxId })); | |||
} | |||
render () { | |||
const { intl, hasUnread, columnId, multiColumn } = this.props; | |||
const pinned = !!columnId; | |||
return ( | |||
<Column ref={this.setRef}> | |||
<ColumnHeader | |||
icon='envelope' | |||
active={hasUnread} | |||
title={intl.formatMessage(messages.title)} | |||
onPin={this.handlePin} | |||
onMove={this.handleMove} | |||
onClick={this.handleHeaderClick} | |||
pinned={pinned} | |||
multiColumn={multiColumn} | |||
> | |||
<ColumnSettingsContainer /> | |||
</ColumnHeader> | |||
<StatusListContainer | |||
trackScroll={!pinned} | |||
scrollKey={`direct_timeline-${columnId}`} | |||
timelineId='direct' | |||
onLoadMore={this.handleLoadMore} | |||
emptyMessage={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any direct messages yet. When you send or receive one, it will show up here." />} | |||
/> | |||
</Column> | |||
); | |||
} | |||
} |