Add remote only to public timeline (#13504)
* Add remote only to public timeline * Fix code style
This commit is contained in:
		| @@ -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); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user