diff --git a/app/soapbox/actions/streaming.js b/app/soapbox/actions/streaming.js
index 3b66a0472..1baa252b0 100644
--- a/app/soapbox/actions/streaming.js
+++ b/app/soapbox/actions/streaming.js
@@ -82,7 +82,8 @@ 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 connectRemoteStream = (instance, { onlyMedia } = {}) => connectTimelineStream(`remote${onlyMedia ? ':media' : ''}:${instance}`, `public:remote${onlyMedia ? ':media' : ''}&instance=${instance}`);
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}`);
-export const connectGroupStream = id => connectTimelineStream(`group:${id}`, `group&group=${id}`);
+export const connectGroupStream = id => connectTimelineStream(`group:${id}`, `group&group=${id}`);
diff --git a/app/soapbox/actions/timelines.js b/app/soapbox/actions/timelines.js
index 67f6b19f8..7ce6320fd 100644
--- a/app/soapbox/actions/timelines.js
+++ b/app/soapbox/actions/timelines.js
@@ -166,6 +166,8 @@ export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => ex
export const expandPublicTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`public${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { max_id: maxId, only_media: !!onlyMedia }, done);
+export const expandRemoteTimeline = (instance, { maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`remote${onlyMedia ? ':media' : ''}:${instance}`, '/api/v1/timelines/public', { local: false, instance: instance, 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 expandDirectTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('direct', '/api/v1/timelines/direct', { max_id: maxId }, done);
diff --git a/app/soapbox/components/status.js b/app/soapbox/components/status.js
index c93ccac22..175d6a0e4 100644
--- a/app/soapbox/components/status.js
+++ b/app/soapbox/components/status.js
@@ -17,7 +17,7 @@ import { HotKeys } from 'react-hotkeys';
import classNames from 'classnames';
import Icon from 'soapbox/components/icon';
import PollContainer from 'soapbox/containers/poll_container';
-import { NavLink } from 'react-router-dom';
+import { Link, NavLink } from 'react-router-dom';
import { getDomain } from 'soapbox/utils/accounts';
import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper';
@@ -459,7 +459,9 @@ class Status extends ImmutablePureComponent {
{favicon &&
-
+
+
+
}
diff --git a/app/soapbox/features/remote_timeline/index.js b/app/soapbox/features/remote_timeline/index.js
new file mode 100644
index 000000000..ee08192ff
--- /dev/null
+++ b/app/soapbox/features/remote_timeline/index.js
@@ -0,0 +1,111 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import PropTypes from 'prop-types';
+import StatusListContainer from '../ui/containers/status_list_container';
+import Column from '../../components/column';
+import HomeColumnHeader from '../../components/home_column_header';
+import IconButton from 'soapbox/components/icon_button';
+import { expandRemoteTimeline } from '../../actions/timelines';
+import { connectRemoteStream } from '../../actions/streaming';
+import { getSettings } from 'soapbox/actions/settings';
+
+const messages = defineMessages({
+ title: { id: 'column.remote', defaultMessage: 'Federated timeline' },
+});
+
+const mapStateToProps = (state, props) => {
+ const instance = props.params.instance;
+ const settings = getSettings(state);
+ const onlyMedia = settings.getIn(['remote', 'other', 'onlyMedia']);
+
+ const timelineId = 'remote';
+
+ return {
+ timelineId,
+ onlyMedia,
+ hasUnread: state.getIn(['timelines', `${timelineId}${onlyMedia ? ':media' : ''}:${instance}`, 'unread']) > 0,
+ instance,
+ };
+};
+
+export default @connect(mapStateToProps)
+@injectIntl
+class RemoteTimeline extends React.PureComponent {
+
+ static contextTypes = {
+ router: PropTypes.object,
+ };
+
+ static propTypes = {
+ dispatch: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ hasUnread: PropTypes.bool,
+ onlyMedia: PropTypes.bool,
+ timelineId: PropTypes.string,
+ instance: PropTypes.string.isRequired,
+ };
+
+ componentDidMount() {
+ const { dispatch, onlyMedia, instance } = this.props;
+ dispatch(expandRemoteTimeline(instance, { onlyMedia }));
+ this.disconnect = dispatch(connectRemoteStream(instance, { onlyMedia }));
+ }
+
+ componentDidUpdate(prevProps) {
+ if (prevProps.onlyMedia !== this.props.onlyMedia) {
+ const { dispatch, onlyMedia, instance } = this.props;
+ this.disconnect();
+
+ dispatch(expandRemoteTimeline(instance, { onlyMedia }));
+ this.disconnect = dispatch(connectRemoteStream(instance, { onlyMedia }));
+ }
+ }
+
+ componentWillUnmount() {
+ if (this.disconnect) {
+ this.disconnect();
+ this.disconnect = null;
+ }
+ }
+
+ handleCloseClick = e => {
+ this.context.router.history.push('/timeline/fediverse');
+ }
+
+ handleLoadMore = maxId => {
+ const { dispatch, onlyMedia, instance } = this.props;
+ dispatch(expandRemoteTimeline(instance, { maxId, onlyMedia }));
+ }
+
+ render() {
+ const { intl, hasUnread, onlyMedia, timelineId, instance } = this.props;
+
+ return (
+
+
+
+
+
+
+
+ }
+ />
+
+ );
+ }
+
+}
diff --git a/app/soapbox/features/status/components/detailed_status.js b/app/soapbox/features/status/components/detailed_status.js
index ee162810e..6f8608d5e 100644
--- a/app/soapbox/features/status/components/detailed_status.js
+++ b/app/soapbox/features/status/components/detailed_status.js
@@ -5,7 +5,7 @@ import Avatar from '../../../components/avatar';
import DisplayName from '../../../components/display_name';
import StatusContent from '../../../components/status_content';
import MediaGallery from '../../../components/media_gallery';
-import { NavLink } from 'react-router-dom';
+import { Link, NavLink } from 'react-router-dom';
import { FormattedDate } from 'react-intl';
import Card from './card';
import ImmutablePureComponent from 'react-immutable-pure-component';
@@ -197,7 +197,9 @@ export default class DetailedStatus extends ImmutablePureComponent {
{favicon &&
-
+
+
+
}
{statusTypeIcon}
diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js
index 9f0085772..8b714fe7e 100644
--- a/app/soapbox/features/ui/index.js
+++ b/app/soapbox/features/ui/index.js
@@ -45,6 +45,7 @@ import {
// GettingStarted,
CommunityTimeline,
PublicTimeline,
+ RemoteTimeline,
AccountTimeline,
AccountGallery,
HomeTimeline,
@@ -208,6 +209,7 @@ class SwitchingColumnsArea extends React.PureComponent {
+
{/*
diff --git a/app/soapbox/features/ui/util/async-components.js b/app/soapbox/features/ui/util/async-components.js
index 01838b156..3cb9e5142 100644
--- a/app/soapbox/features/ui/util/async-components.js
+++ b/app/soapbox/features/ui/util/async-components.js
@@ -18,6 +18,10 @@ export function PublicTimeline() {
return import(/* webpackChunkName: "features/public_timeline" */'../../public_timeline');
}
+export function RemoteTimeline() {
+ return import(/* webpackChunkName: "features/remote_timeline" */'../../remote_timeline');
+}
+
export function CommunityTimeline() {
return import(/* webpackChunkName: "features/community_timeline" */'../../community_timeline');
}
diff --git a/app/styles/components/columns.scss b/app/styles/components/columns.scss
index a3f2dac95..a2f430538 100644
--- a/app/styles/components/columns.scss
+++ b/app/styles/components/columns.scss
@@ -716,3 +716,13 @@
color: white;
}
}
+
+.timeline-filter-message {
+ background-color: var(--brand-color--faint);
+ color: var(--primary-text-color);
+ padding: 15px 20px;
+
+ .icon-button {
+ margin-right: 8px;
+ }
+}