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 (
+
+ );
+ }
+
+}
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 {