Add remote only to public timeline (#13504)
* Add remote only to public timeline * Fix code style
This commit is contained in:
		
							parent
							
								
									e9ecbca70d
								
							
						
					
					
						commit
						26b08a3c54
					
				
					 10 changed files with 106 additions and 21 deletions
				
			
		| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue