diff --git a/app/soapbox/features/federation_restrictions/components/instance_restrictions.js b/app/soapbox/features/federation_restrictions/components/instance_restrictions.js new file mode 100644 index 000000000..595b5a418 --- /dev/null +++ b/app/soapbox/features/federation_restrictions/components/instance_restrictions.js @@ -0,0 +1,177 @@ +'use strict'; + +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { FormattedMessage } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import Icon from 'soapbox/components/icon'; + +const hasRestrictions = remoteInstance => { + return remoteInstance + .get('federation') + .deleteAll(['accept', 'reject_deletes', 'report_removal']) + .reduce((acc, value) => acc || value, false); +}; + +const mapStateToProps = state => { + return { + instance: state.get('instance'), + }; +}; + +export default @connect(mapStateToProps) +class InstanceRestrictions extends ImmutablePureComponent { + + static propTypes = { + intl: PropTypes.object.isRequired, + remoteInstance: ImmutablePropTypes.map.isRequired, + instance: ImmutablePropTypes.map, + }; + + renderRestrictions = () => { + const { remoteInstance } = this.props; + const items = []; + + const { + avatar_removal, + banner_removal, + federated_timeline_removal, + followers_only, + media_nsfw, + media_removal, + } = remoteInstance.get('federation').toJS(); + + const fullMediaRemoval = media_removal && avatar_removal && banner_removal; + const partialMediaRemoval = media_removal || avatar_removal || banner_removal; + + if (followers_only) { + items.push(( +
+
+ +
+
+ +
+
+ )); + } else if (federated_timeline_removal) { + items.push(( +
+
+ +
+
+ +
+
+ )); + } + + if (fullMediaRemoval) { + items.push(( +
+
+ +
+
+ +
+
+ )); + } else if (partialMediaRemoval) { + items.push(( +
+
+ +
+
+ +
+
+ )); + } + + if (!fullMediaRemoval && media_nsfw) { + items.push(( +
+
+ +
+
+ +
+
+ )); + } + + return items; + } + + renderContent = () => { + const { instance, remoteInstance } = this.props; + if (!instance || !remoteInstance) return null; + + const host = remoteInstance.get('host'); + const siteTitle = instance.get('title'); + + if (remoteInstance.getIn(['federation', 'reject']) === true) { + return ( +
+ + +
+ ); + } else if (hasRestrictions(remoteInstance)) { + return [ + ( +
+ +
+ ), + this.renderRestrictions(), + ]; + } else { + return ( +
+ + +
+ ); + } + } + + render() { + return
{this.renderContent()}
; + } + +} diff --git a/app/soapbox/features/federation_restrictions/components/restricted_instance.js b/app/soapbox/features/federation_restrictions/components/restricted_instance.js new file mode 100644 index 000000000..cc62f0d7d --- /dev/null +++ b/app/soapbox/features/federation_restrictions/components/restricted_instance.js @@ -0,0 +1,59 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { makeGetRemoteInstance } from 'soapbox/selectors'; +import classNames from 'classnames'; +import InstanceRestrictions from './instance_restrictions'; +import Icon from 'soapbox/components/icon'; + +const getRemoteInstance = makeGetRemoteInstance(); + +const mapStateToProps = (state, ownProps) => { + return { + remoteInstance: getRemoteInstance(state, ownProps.host), + }; +}; + +export default @connect(mapStateToProps) +class RestrictedInstance extends ImmutablePureComponent { + + static propTypes = { + host: PropTypes.string.isRequired, + } + + state = { + expanded: false, + } + + toggleExpanded = e => { + this.setState({ expanded: !this.state.expanded }); + e.preventDefault(); + } + + render() { + const { remoteInstance } = this.props; + const { expanded } = this.state; + + return ( +
+ +
+ +
+
+ {remoteInstance.get('host')} +
+
+
+ +
+
+ ); + } + +} diff --git a/app/soapbox/features/federation_restrictions/index.js b/app/soapbox/features/federation_restrictions/index.js new file mode 100644 index 000000000..fa1987b1e --- /dev/null +++ b/app/soapbox/features/federation_restrictions/index.js @@ -0,0 +1,72 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { defineMessages, injectIntl } from 'react-intl'; +import PropTypes from 'prop-types'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import Column from '../ui/components/column'; +import { createSelector } from 'reselect'; +import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable'; +import RestrictedInstance from './components/restricted_instance'; +import Accordion from 'soapbox/features/ui/components/accordion'; + +const getHosts = createSelector([ + state => state.getIn(['instance', 'pleroma', 'metadata', 'federation', 'mrf_simple'], ImmutableMap()), +], (simplePolicy) => { + return simplePolicy + .deleteAll(['accept', 'reject_deletes', 'report_removal']) + .reduce((acc, hosts) => acc.union(hosts), ImmutableOrderedSet()) + .sort(); +}); + +const messages = defineMessages({ + heading: { id: 'column.federation_restrictions', defaultMessage: 'Federation Restrictions' }, + boxTitle: { id: 'federation_restrictions.explanation_box.title', defaultMessage: 'Instance-specific policies' }, + boxMessage: { id: 'federation_restrictions.explanation_box.message', defaultMessage: 'Normally servers on the Fediverse can communicate freely. {siteTitle} has imposed restrictions on the following servers.' }, +}); + +const mapStateToProps = state => ({ + siteTitle: state.getIn(['instance', 'title']), + hosts: getHosts(state), +}); + +export default @connect(mapStateToProps) +@injectIntl +class FederationRestrictions extends ImmutablePureComponent { + + static propTypes = { + intl: PropTypes.object.isRequired, + }; + + state = { + explanationBoxExpanded: true, + } + + toggleExplanationBox = setting => { + this.setState({ explanationBoxExpanded: setting }); + } + + render() { + const { intl, hosts, siteTitle } = this.props; + const { explanationBoxExpanded } = this.state; + + return ( + +
+ + {intl.formatMessage(messages.boxMessage, { siteTitle })} + + +
+ +
+ {hosts.map(host => )} +
+
+ ); + } + +} diff --git a/app/soapbox/features/ui/components/instance_info_panel.js b/app/soapbox/features/ui/components/instance_info_panel.js index f27105ee9..ee8440262 100644 --- a/app/soapbox/features/ui/components/instance_info_panel.js +++ b/app/soapbox/features/ui/components/instance_info_panel.js @@ -6,15 +6,8 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import Icon from 'soapbox/components/icon'; import { makeGetRemoteInstance } from 'soapbox/selectors'; - -const hasRestrictions = remoteInstance => { - return remoteInstance - .get('federation') - .deleteAll(['accept', 'reject_deletes', 'report_removal']) - .reduce((acc, value) => acc || value, false); -}; +import InstanceRestrictions from 'soapbox/features/federation_restrictions/components/instance_restrictions'; const getRemoteInstance = makeGetRemoteInstance(); @@ -35,144 +28,9 @@ class InstanceInfoPanel extends ImmutablePureComponent { remoteInstance: ImmutablePropTypes.map, }; - renderRestrictions = () => { - const { remoteInstance } = this.props; - const items = []; - - const { - avatar_removal, - banner_removal, - federated_timeline_removal, - followers_only, - media_nsfw, - media_removal, - } = remoteInstance.get('federation').toJS(); - - const fullMediaRemoval = media_removal && avatar_removal && banner_removal; - const partialMediaRemoval = media_removal || avatar_removal || banner_removal; - - if (followers_only) { - items.push(( -
-
- -
-
- -
-
- )); - } else if (federated_timeline_removal) { - items.push(( -
-
- -
-
- -
-
- )); - } - - if (fullMediaRemoval) { - items.push(( -
-
- -
-
- -
-
- )); - } else if (partialMediaRemoval) { - items.push(( -
-
- -
-
- -
-
- )); - } - - if (!fullMediaRemoval && media_nsfw) { - items.push(( -
-
- -
-
- -
-
- )); - } - - return items; - } - - renderContent = () => { - const { host, instance, remoteInstance } = this.props; - if (!instance || !remoteInstance) return null; - - if (remoteInstance.getIn(['federation', 'reject']) === true) { - return ( -
- - -
- ); - } else if (hasRestrictions(remoteInstance)) { - return [ - ( -
- -
- ), - this.renderRestrictions(), - ]; - } else { - return ( -
- - -
- ); - } - } - render() { + const { remoteInstance } = this.props; + return (
@@ -182,7 +40,7 @@ class InstanceInfoPanel extends ImmutablePureComponent {
- {this.renderContent()} +
); diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index fe6745b69..49d58aca7 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -97,6 +97,7 @@ import { CryptoDonate, ScheduledStatuses, UserIndex, + FederationRestrictions, } from './util/async-components'; // Dummy import, to make sure that ends up in the application bundle. @@ -272,6 +273,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 c1bb39301..c1b34b608 100644 --- a/app/soapbox/features/ui/util/async-components.js +++ b/app/soapbox/features/ui/util/async-components.js @@ -245,3 +245,7 @@ export function ScheduledStatuses() { export function UserIndex() { return import(/* webpackChunkName: "features/admin/user_index" */'../../admin/user_index'); } + +export function FederationRestrictions() { + return import(/* webpackChunkName: "features/federation_restrictions" */'../../federation_restrictions'); +} diff --git a/app/soapbox/selectors/index.js b/app/soapbox/selectors/index.js index b1bd570f7..5ba6f6088 100644 --- a/app/soapbox/selectors/index.js +++ b/app/soapbox/selectors/index.js @@ -230,10 +230,12 @@ const getSimplePolicy = (state, host) => ( export const makeGetRemoteInstance = () => { return createSelector([ + (state, host) => host, getRemoteInstanceFavicon, getSimplePolicy, - ], (favicon, federation) => { + ], (host, favicon, federation) => { return ImmutableMap({ + host, favicon, federation, }); diff --git a/app/styles/application.scss b/app/styles/application.scss index f6611fdf0..d247b3a7b 100644 --- a/app/styles/application.scss +++ b/app/styles/application.scss @@ -83,6 +83,7 @@ @import 'components/crypto-donate'; @import 'components/datepicker'; @import 'components/remote-timeline'; +@import 'components/federation-restrictions'; // Holiday @import 'holiday/halloween'; diff --git a/app/styles/components/federation-restrictions.scss b/app/styles/components/federation-restrictions.scss new file mode 100644 index 000000000..dab37d89b --- /dev/null +++ b/app/styles/components/federation-restrictions.scss @@ -0,0 +1,57 @@ +.federation-restrictions { + padding: 15px; +} + +.restricted-instance { + &__header { + padding: 10px 0; + display: flex; + text-decoration: none; + color: var(--primary-text-color); + } + + &__icon { + width: 16px; + } + + &--expanded &__icon i.fa { + transform: translateX(-3px); + } + + &--reject &__host { + text-decoration: line-through; + } + + &__restrictions { + height: 0; + overflow: hidden; + } + + &--expanded &__restrictions { + height: auto; + } + + .instance-restrictions { + padding: 5px 0 5px 15px; + border-left: 3px solid hsla(var(--primary-text-color_hsl), 0.4); + color: var(--primary-text-color--faint); + margin-bottom: 15px; + + .federation-restriction { + padding: 7px 0; + font-size: 14px; + } + + &__message { + margin-bottom: 10px; + + i.fa { + padding-right: 10px; + } + + &:last-child { + margin-bottom: 0; + } + } + } +} diff --git a/app/styles/components/remote-timeline.scss b/app/styles/components/remote-timeline.scss index 3fb00e731..76e90b38d 100644 --- a/app/styles/components/remote-timeline.scss +++ b/app/styles/components/remote-timeline.scss @@ -1,16 +1,18 @@ .instance-federation-panel { - &__message { - margin-bottom: 15px; - - i.fa { - padding-right: 10px; - } - } - .wtf-panel__content { box-sizing: border-box; padding: 15px; } + + .instance-restrictions { + &__message { + margin-bottom: 15px; + + i.fa { + padding-right: 10px; + } + } + } } .federation-restriction {