* Add remote only to public timeline * Fix code stylemain
@@ -39,7 +39,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController | |||
end | |||
def public_timeline_statuses | |||
Status.as_public_timeline(current_account, truthy_param?(:local)) | |||
Status.as_public_timeline(current_account, truthy_param?(:remote) ? :remote : truthy_param?(:local)) | |||
end | |||
def insert_pagination_headers | |||
@@ -47,7 +47,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController | |||
end | |||
def pagination_params(core_params) | |||
params.slice(:local, :limit, :only_media).permit(:local, :limit, :only_media).merge(core_params) | |||
params.slice(:local, :remote, :limit, :only_media).permit(:local, :remote, :limit, :only_media).merge(core_params) | |||
end | |||
def next_path | |||
@@ -73,7 +73,7 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => { | |||
export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification); | |||
export const connectCommunityStream = ({ onlyMedia } = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`); | |||
export const connectPublicStream = ({ onlyMedia } = {}) => connectTimelineStream(`public${onlyMedia ? ':media' : ''}`, `public${onlyMedia ? ':media' : ''}`); | |||
export const connectPublicStream = ({ onlyMedia, onlyRemote } = {}) => connectTimelineStream(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, `public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`); | |||
export const connectHashtagStream = (id, tag, accept) => connectTimelineStream(`hashtag:${id}`, `hashtag&tag=${tag}`, null, accept); | |||
export const connectDirectStream = () => connectTimelineStream('direct', 'direct'); | |||
export const connectListStream = id => connectTimelineStream(`list:${id}`, `list&list=${id}`); |
@@ -107,7 +107,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) { | |||
}; | |||
export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done); | |||
export const expandPublicTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`public${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { max_id: maxId, only_media: !!onlyMedia }, done); | |||
export const expandPublicTimeline = ({ maxId, onlyMedia, onlyRemote } = {}, done = noOp) => expandTimeline(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { remote: !!onlyRemote, max_id: maxId, only_media: !!onlyMedia }, done); | |||
export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done); | |||
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId }); | |||
export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true }); | |||
@@ -0,0 +1,30 @@ | |||
import React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import ImmutablePropTypes from 'react-immutable-proptypes'; | |||
import { injectIntl, FormattedMessage } from 'react-intl'; | |||
import SettingToggle from '../../notifications/components/setting_toggle'; | |||
export default @injectIntl | |||
class ColumnSettings extends React.PureComponent { | |||
static propTypes = { | |||
settings: ImmutablePropTypes.map.isRequired, | |||
onChange: PropTypes.func.isRequired, | |||
intl: PropTypes.object.isRequired, | |||
columnId: PropTypes.string, | |||
}; | |||
render () { | |||
const { settings, onChange } = this.props; | |||
return ( | |||
<div> | |||
<div className='column-settings__row'> | |||
<SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} /> | |||
<SettingToggle settings={settings} settingPath={['other', 'onlyRemote']} onChange={onChange} label={<FormattedMessage id='community.column_settings.remote_only' defaultMessage='Remote only' />} /> | |||
</div> | |||
</div> | |||
); | |||
} | |||
} |
@@ -1,5 +1,5 @@ | |||
import { connect } from 'react-redux'; | |||
import ColumnSettings from '../../community_timeline/components/column_settings'; | |||
import ColumnSettings from '../components/column_settings'; | |||
import { changeSetting } from '../../../actions/settings'; | |||
import { changeColumnParams } from '../../../actions/columns'; | |||
@@ -19,11 +19,13 @@ const mapStateToProps = (state, { columnId }) => { | |||
const columns = state.getIn(['settings', 'columns']); | |||
const index = columns.findIndex(c => c.get('uuid') === uuid); | |||
const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']); | |||
const onlyRemote = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyRemote']) : state.getIn(['settings', 'public', 'other', 'onlyRemote']); | |||
const timelineState = state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`]); | |||
return { | |||
hasUnread: !!timelineState && timelineState.get('unread') > 0, | |||
onlyMedia, | |||
onlyRemote, | |||
}; | |||
}; | |||
@@ -47,15 +49,16 @@ class PublicTimeline extends React.PureComponent { | |||
multiColumn: PropTypes.bool, | |||
hasUnread: PropTypes.bool, | |||
onlyMedia: PropTypes.bool, | |||
onlyRemote: PropTypes.bool, | |||
}; | |||
handlePin = () => { | |||
const { columnId, dispatch, onlyMedia } = this.props; | |||
const { columnId, dispatch, onlyMedia, onlyRemote } = this.props; | |||
if (columnId) { | |||
dispatch(removeColumn(columnId)); | |||
} else { | |||
dispatch(addColumn('PUBLIC', { other: { onlyMedia } })); | |||
dispatch(addColumn(onlyRemote ? 'REMOTE' : 'PUBLIC', { other: { onlyMedia, onlyRemote } })); | |||
} | |||
} | |||
@@ -69,19 +72,19 @@ class PublicTimeline extends React.PureComponent { | |||
} | |||
componentDidMount () { | |||
const { dispatch, onlyMedia } = this.props; | |||
const { dispatch, onlyMedia, onlyRemote } = this.props; | |||
dispatch(expandPublicTimeline({ onlyMedia })); | |||
this.disconnect = dispatch(connectPublicStream({ onlyMedia })); | |||
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote })); | |||
this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote })); | |||
} | |||
componentDidUpdate (prevProps) { | |||
if (prevProps.onlyMedia !== this.props.onlyMedia) { | |||
const { dispatch, onlyMedia } = this.props; | |||
if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote) { | |||
const { dispatch, onlyMedia, onlyRemote } = this.props; | |||
this.disconnect(); | |||
dispatch(expandPublicTimeline({ onlyMedia })); | |||
this.disconnect = dispatch(connectPublicStream({ onlyMedia })); | |||
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote })); | |||
this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote })); | |||
} | |||
} | |||
@@ -97,13 +100,13 @@ class PublicTimeline extends React.PureComponent { | |||
} | |||
handleLoadMore = maxId => { | |||
const { dispatch, onlyMedia } = this.props; | |||
const { dispatch, onlyMedia, onlyRemote } = this.props; | |||
dispatch(expandPublicTimeline({ maxId, onlyMedia })); | |||
dispatch(expandPublicTimeline({ maxId, onlyMedia, onlyRemote })); | |||
} | |||
render () { | |||
const { intl, shouldUpdateScroll, columnId, hasUnread, multiColumn, onlyMedia } = this.props; | |||
const { intl, shouldUpdateScroll, columnId, hasUnread, multiColumn, onlyMedia, onlyRemote } = this.props; | |||
const pinned = !!columnId; | |||
return ( | |||
@@ -122,7 +125,7 @@ class PublicTimeline extends React.PureComponent { | |||
</ColumnHeader> | |||
<StatusListContainer | |||
timelineId={`public${onlyMedia ? ':media' : ''}`} | |||
timelineId={`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`} | |||
onLoadMore={this.handleLoadMore} | |||
trackScroll={!pinned} | |||
scrollKey={`public_timeline-${columnId}`} | |||
@@ -37,6 +37,7 @@ const componentMap = { | |||
'HOME': HomeTimeline, | |||
'NOTIFICATIONS': Notifications, | |||
'PUBLIC': PublicTimeline, | |||
'REMOTE': PublicTimeline, | |||
'COMMUNITY': CommunityTimeline, | |||
'HASHTAG': HashtagTimeline, | |||
'DIRECT': DirectTimeline, | |||
@@ -284,7 +284,7 @@ class Status < ApplicationRecord | |||
def as_public_timeline(account = nil, local_only = false) | |||
query = timeline_scope(local_only).without_replies | |||
apply_timeline_filters(query, account, local_only) | |||
apply_timeline_filters(query, account, [:local, true].include?(local_only)) | |||
end | |||
def as_tag_timeline(tag, account = nil, local_only = false) | |||
@@ -376,8 +376,16 @@ class Status < ApplicationRecord | |||
private | |||
def timeline_scope(local_only = false) | |||
starting_scope = local_only ? Status.local : Status | |||
def timeline_scope(scope = false) | |||
starting_scope = case scope | |||
when :local, true | |||
Status.local | |||
when :remote | |||
Status.remote | |||
else | |||
Status | |||
end | |||
starting_scope | |||
.with_public_visibility | |||
.without_reblogs | |||
@@ -374,6 +374,33 @@ RSpec.describe Status, type: :model do | |||
end | |||
end | |||
context 'with a remote_only option set' do | |||
let!(:local_account) { Fabricate(:account, domain: nil) } | |||
let!(:remote_account) { Fabricate(:account, domain: 'test.com') } | |||
let!(:local_status) { Fabricate(:status, account: local_account) } | |||
let!(:remote_status) { Fabricate(:status, account: remote_account) } | |||
subject { Status.as_public_timeline(viewer, :remote) } | |||
context 'without a viewer' do | |||
let(:viewer) { nil } | |||
it 'does not include local instances statuses' do | |||
expect(subject).not_to include(local_status) | |||
expect(subject).to include(remote_status) | |||
end | |||
end | |||
context 'with a viewer' do | |||
let(:viewer) { Fabricate(:account, username: 'viewer') } | |||
it 'does not include local instances statuses' do | |||
expect(subject).not_to include(local_status) | |||
expect(subject).to include(remote_status) | |||
end | |||
end | |||
end | |||
describe 'with an account passed in' do | |||
before do | |||
@account = Fabricate(:account) | |||
@@ -266,6 +266,8 @@ const startWorker = (workerId) => { | |||
'public:media', | |||
'public:local', | |||
'public:local:media', | |||
'public:remote', | |||
'public:remote:media', | |||
'hashtag', | |||
'hashtag:local', | |||
]; | |||
@@ -297,6 +299,7 @@ const startWorker = (workerId) => { | |||
const PUBLIC_ENDPOINTS = [ | |||
'/api/v1/streaming/public', | |||
'/api/v1/streaming/public/local', | |||
'/api/v1/streaming/public/remote', | |||
'/api/v1/streaming/hashtag', | |||
'/api/v1/streaming/hashtag/local', | |||
]; | |||
@@ -535,6 +538,13 @@ const startWorker = (workerId) => { | |||
streamFrom(channel, req, streamToHttp(req, res), streamHttpEnd(req), true); | |||
}); | |||
app.get('/api/v1/streaming/public/remote', (req, res) => { | |||
const onlyMedia = req.query.only_media === '1' || req.query.only_media === 'true'; | |||
const channel = onlyMedia ? 'timeline:public:remote:media' : 'timeline:public:remote'; | |||
streamFrom(channel, req, streamToHttp(req, res), streamHttpEnd(req), true); | |||
}); | |||
app.get('/api/v1/streaming/direct', (req, res) => { | |||
const channel = `timeline:direct:${req.accountId}`; | |||
streamFrom(channel, req, streamToHttp(req, res), streamHttpEnd(req, subscriptionHeartbeat(channel)), true); | |||
@@ -599,12 +609,18 @@ const startWorker = (workerId) => { | |||
case 'public:local': | |||
streamFrom('timeline:public:local', req, streamToWs(req, ws), streamWsEnd(req, ws), true); | |||
break; | |||
case 'public:remote': | |||
streamFrom('timeline:public:remote', req, streamToWs(req, ws), streamWsEnd(req, ws), true); | |||
break; | |||
case 'public:media': | |||
streamFrom('timeline:public:media', req, streamToWs(req, ws), streamWsEnd(req, ws), true); | |||
break; | |||
case 'public:local:media': | |||
streamFrom('timeline:public:local:media', req, streamToWs(req, ws), streamWsEnd(req, ws), true); | |||
break; | |||
case 'public:remote:media': | |||
streamFrom('timeline:public:remote:media', req, streamToWs(req, ws), streamWsEnd(req, ws), true); | |||
break; | |||
case 'direct': | |||
channel = `timeline:direct:${req.accountId}`; | |||
streamFrom(channel, req, streamToWs(req, ws), streamWsEnd(req, ws, subscriptionHeartbeat(channel)), true); | |||