From 1794e5a72d1bd41fc91028c0a76843558fb51016 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 24 Dec 2020 16:20:58 -0600 Subject: [PATCH 1/4] Rudimentary remote timelines --- app/soapbox/actions/streaming.js | 3 +- app/soapbox/actions/timelines.js | 2 + .../components/column_settings.js | 36 +++++++ .../containers/column_settings_container.js | 17 +++ app/soapbox/features/remote_timeline/index.js | 101 ++++++++++++++++++ app/soapbox/features/ui/index.js | 2 + .../features/ui/util/async-components.js | 4 + 7 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 app/soapbox/features/remote_timeline/components/column_settings.js create mode 100644 app/soapbox/features/remote_timeline/containers/column_settings_container.js create mode 100644 app/soapbox/features/remote_timeline/index.js 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/features/remote_timeline/components/column_settings.js b/app/soapbox/features/remote_timeline/components/column_settings.js new file mode 100644 index 000000000..1d7bd7359 --- /dev/null +++ b/app/soapbox/features/remote_timeline/components/column_settings.js @@ -0,0 +1,36 @@ +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, + }; + + render() { + const { settings, onChange } = this.props; + + return ( +
+
+ } /> +
+ +
+ } /> +
+ +
+ } /> +
+
+ ); + } + +} diff --git a/app/soapbox/features/remote_timeline/containers/column_settings_container.js b/app/soapbox/features/remote_timeline/containers/column_settings_container.js new file mode 100644 index 000000000..ac001bcab --- /dev/null +++ b/app/soapbox/features/remote_timeline/containers/column_settings_container.js @@ -0,0 +1,17 @@ +import { connect } from 'react-redux'; +import ColumnSettings from '../components/column_settings'; +import { getSettings, changeSetting } from '../../../actions/settings'; + +const mapStateToProps = state => ({ + settings: getSettings(state).get('public'), +}); + +const mapDispatchToProps = (dispatch) => { + return { + onChange(key, checked) { + dispatch(changeSetting(['public', ...key], checked)); + }, + }; +}; + +export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings); diff --git a/app/soapbox/features/remote_timeline/index.js b/app/soapbox/features/remote_timeline/index.js new file mode 100644 index 000000000..b5d6cdb8f --- /dev/null +++ b/app/soapbox/features/remote_timeline/index.js @@ -0,0 +1,101 @@ +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 ColumnSettingsContainer from './containers/column_settings_container'; +import HomeColumnHeader from '../../components/home_column_header'; +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; + } + } + + 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/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'); } From b6bec67eeac546a4096d263552b6380ea0bd23fb Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 24 Dec 2020 16:39:57 -0600 Subject: [PATCH 2/4] RemoteTimeline: add filter box --- app/soapbox/features/remote_timeline/index.js | 15 ++++++++++++++- app/styles/components/columns.scss | 10 ++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/soapbox/features/remote_timeline/index.js b/app/soapbox/features/remote_timeline/index.js index b5d6cdb8f..7094c8ddb 100644 --- a/app/soapbox/features/remote_timeline/index.js +++ b/app/soapbox/features/remote_timeline/index.js @@ -6,6 +6,7 @@ import StatusListContainer from '../ui/containers/status_list_container'; import Column from '../../components/column'; import ColumnSettingsContainer from './containers/column_settings_container'; 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'; @@ -69,6 +70,10 @@ class RemoteTimeline extends React.PureComponent { } } + handleCloseClick = e => { + this.context.router.history.push('/timeline/fediverse'); + } + handleLoadMore = maxId => { const { dispatch, onlyMedia, instance } = this.props; dispatch(expandRemoteTimeline(instance, { maxId, onlyMedia })); @@ -82,6 +87,14 @@ class RemoteTimeline extends React.PureComponent { +
+ + +
} 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; + } +} From a4bb347151dbd4e329bfa00bd9e9bb73241ad838 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 24 Dec 2020 16:40:33 -0600 Subject: [PATCH 3/4] RemoteTimeline: remove column settings --- .../components/column_settings.js | 36 ------------------- .../containers/column_settings_container.js | 17 --------- app/soapbox/features/remote_timeline/index.js | 5 +-- 3 files changed, 1 insertion(+), 57 deletions(-) delete mode 100644 app/soapbox/features/remote_timeline/components/column_settings.js delete mode 100644 app/soapbox/features/remote_timeline/containers/column_settings_container.js diff --git a/app/soapbox/features/remote_timeline/components/column_settings.js b/app/soapbox/features/remote_timeline/components/column_settings.js deleted file mode 100644 index 1d7bd7359..000000000 --- a/app/soapbox/features/remote_timeline/components/column_settings.js +++ /dev/null @@ -1,36 +0,0 @@ -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, - }; - - render() { - const { settings, onChange } = this.props; - - return ( -
-
- } /> -
- -
- } /> -
- -
- } /> -
-
- ); - } - -} diff --git a/app/soapbox/features/remote_timeline/containers/column_settings_container.js b/app/soapbox/features/remote_timeline/containers/column_settings_container.js deleted file mode 100644 index ac001bcab..000000000 --- a/app/soapbox/features/remote_timeline/containers/column_settings_container.js +++ /dev/null @@ -1,17 +0,0 @@ -import { connect } from 'react-redux'; -import ColumnSettings from '../components/column_settings'; -import { getSettings, changeSetting } from '../../../actions/settings'; - -const mapStateToProps = state => ({ - settings: getSettings(state).get('public'), -}); - -const mapDispatchToProps = (dispatch) => { - return { - onChange(key, checked) { - dispatch(changeSetting(['public', ...key], checked)); - }, - }; -}; - -export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings); diff --git a/app/soapbox/features/remote_timeline/index.js b/app/soapbox/features/remote_timeline/index.js index 7094c8ddb..ee08192ff 100644 --- a/app/soapbox/features/remote_timeline/index.js +++ b/app/soapbox/features/remote_timeline/index.js @@ -4,7 +4,6 @@ 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 ColumnSettingsContainer from './containers/column_settings_container'; import HomeColumnHeader from '../../components/home_column_header'; import IconButton from 'soapbox/components/icon_button'; import { expandRemoteTimeline } from '../../actions/timelines'; @@ -84,9 +83,7 @@ class RemoteTimeline extends React.PureComponent { return ( - - - +
Date: Thu, 24 Dec 2020 16:57:53 -0600 Subject: [PATCH 4/4] RemoteTimeline: click favicon to visit remote timeline --- app/soapbox/components/status.js | 6 ++++-- app/soapbox/features/status/components/detailed_status.js | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) 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/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}