From d922c37891479b48da7a4de2555c639a004813f2 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 25 Jul 2021 14:50:22 -0500 Subject: [PATCH 1/6] FederationRestrictions: display a list of hosts with restrictions --- .../features/federation_restrictions/index.js | 50 +++++++++++++++++++ app/soapbox/features/ui/index.js | 2 + .../features/ui/util/async-components.js | 4 ++ 3 files changed, 56 insertions(+) create mode 100644 app/soapbox/features/federation_restrictions/index.js diff --git a/app/soapbox/features/federation_restrictions/index.js b/app/soapbox/features/federation_restrictions/index.js new file mode 100644 index 000000000..4056da630 --- /dev/null +++ b/app/soapbox/features/federation_restrictions/index.js @@ -0,0 +1,50 @@ +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'; + +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' }, +}); + +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, + }; + + render() { + const { intl, hosts } = this.props; + + return ( + +
+
    + {hosts.map(host =>
  • {host}
  • )} +
+
+
+ ); + } + +} 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'); +} From 155b342081f48b88d2e64f58f465b60eabd31117 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 25 Jul 2021 15:08:14 -0500 Subject: [PATCH 2/6] FederationRestrictions: add RestrictedInstance component --- .../components/restricted_instance.js | 33 +++++++++++++++++++ .../features/federation_restrictions/index.js | 5 ++- app/soapbox/selectors/index.js | 4 ++- app/styles/application.scss | 2 +- .../components/federation-restrictions.scss | 11 +++++++ 5 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 app/soapbox/features/federation_restrictions/components/restricted_instance.js create mode 100644 app/styles/components/federation-restrictions.scss 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..30775c10c --- /dev/null +++ b/app/soapbox/features/federation_restrictions/components/restricted_instance.js @@ -0,0 +1,33 @@ +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'; + +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, + } + + render() { + const { remoteInstance } = this.props; + + return ( +
+ {remoteInstance.get('host')} +
+ ); + } + +} diff --git a/app/soapbox/features/federation_restrictions/index.js b/app/soapbox/features/federation_restrictions/index.js index 4056da630..13cbfdaaa 100644 --- a/app/soapbox/features/federation_restrictions/index.js +++ b/app/soapbox/features/federation_restrictions/index.js @@ -6,6 +6,7 @@ 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'; const getHosts = createSelector([ state => state.getIn(['instance', 'pleroma', 'metadata', 'federation', 'mrf_simple'], ImmutableMap()), @@ -39,9 +40,7 @@ class FederationRestrictions extends ImmutablePureComponent { return (
-
    - {hosts.map(host =>
  • {host}
  • )} -
+ {hosts.map(host => )}
); 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..7bdea4c13 100644 --- a/app/styles/application.scss +++ b/app/styles/application.scss @@ -83,6 +83,6 @@ @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..5b869c6d5 --- /dev/null +++ b/app/styles/components/federation-restrictions.scss @@ -0,0 +1,11 @@ +.federation-restrictions { + padding: 15px; +} + +.restricted-instance { + padding: 10px 0; + + &--reject { + text-decoration: line-through; + } +} From 075eeee9eee2dbdb60850f3266c8c65db57a9ee3 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 25 Jul 2021 16:02:38 -0500 Subject: [PATCH 3/6] FederationRestrictions: make RestrictedInstance function like an accordion --- .../components/instance_restrictions.js | 177 ++++++++++++++++++ .../components/restricted_instance.js | 30 ++- .../components/federation-restrictions.scss | 50 ++++- 3 files changed, 253 insertions(+), 4 deletions(-) create mode 100644 app/soapbox/features/federation_restrictions/components/instance_restrictions.js 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 index 30775c10c..cc62f0d7d 100644 --- a/app/soapbox/features/federation_restrictions/components/restricted_instance.js +++ b/app/soapbox/features/federation_restrictions/components/restricted_instance.js @@ -4,6 +4,8 @@ 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(); @@ -20,12 +22,36 @@ class RestrictedInstance extends ImmutablePureComponent { 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/styles/components/federation-restrictions.scss b/app/styles/components/federation-restrictions.scss index 5b869c6d5..dab37d89b 100644 --- a/app/styles/components/federation-restrictions.scss +++ b/app/styles/components/federation-restrictions.scss @@ -3,9 +3,55 @@ } .restricted-instance { - padding: 10px 0; + &__header { + padding: 10px 0; + display: flex; + text-decoration: none; + color: var(--primary-text-color); + } - &--reject { + &__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; + } + } + } } From 4599d9e39ff074a2b2bbf105d41c181a76228ee2 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 25 Jul 2021 16:16:37 -0500 Subject: [PATCH 4/6] FederationRestrictions: add explanation box --- .../features/federation_restrictions/index.js | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/app/soapbox/features/federation_restrictions/index.js b/app/soapbox/features/federation_restrictions/index.js index 13cbfdaaa..fa1987b1e 100644 --- a/app/soapbox/features/federation_restrictions/index.js +++ b/app/soapbox/features/federation_restrictions/index.js @@ -7,6 +7,7 @@ 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()), @@ -19,6 +20,8 @@ const getHosts = createSelector([ 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 => ({ @@ -34,11 +37,31 @@ class FederationRestrictions extends ImmutablePureComponent { intl: PropTypes.object.isRequired, }; + state = { + explanationBoxExpanded: true, + } + + toggleExplanationBox = setting => { + this.setState({ explanationBoxExpanded: setting }); + } + render() { - const { intl, hosts } = this.props; + const { intl, hosts, siteTitle } = this.props; + const { explanationBoxExpanded } = this.state; return ( +
+ + {intl.formatMessage(messages.boxMessage, { siteTitle })} + + +
+
{hosts.map(host => )}
From 61a4bc90b8e9d2f43717ea0841fb212ec1e28904 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 25 Jul 2021 16:27:37 -0500 Subject: [PATCH 5/6] InstanceInfoPanel: refactor to reuse InstanceRestrictions component --- .../ui/components/instance_info_panel.js | 150 +----------------- app/styles/components/remote-timeline.scss | 18 ++- 2 files changed, 14 insertions(+), 154 deletions(-) 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/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 { From b84631e0296e732c9a7a4efcbb0f2f9f868f2ccf Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 25 Jul 2021 16:37:31 -0500 Subject: [PATCH 6/6] Add back missing line break scss --- app/styles/application.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/styles/application.scss b/app/styles/application.scss index 7bdea4c13..d247b3a7b 100644 --- a/app/styles/application.scss +++ b/app/styles/application.scss @@ -84,5 +84,6 @@ @import 'components/datepicker'; @import 'components/remote-timeline'; @import 'components/federation-restrictions'; + // Holiday @import 'holiday/halloween';