diff --git a/app/soapbox/actions/instance.js b/app/soapbox/actions/instance.js index 23d628872..94b5da79d 100644 --- a/app/soapbox/actions/instance.js +++ b/app/soapbox/actions/instance.js @@ -9,7 +9,7 @@ export const NODEINFO_FETCH_FAIL = 'NODEINFO_FETCH_FAIL'; export function fetchInstance() { return (dispatch, getState) => { - api(getState).get('/api/v1/instance').then(response => { + return api(getState).get('/api/v1/instance').then(response => { dispatch(importInstance(response.data)); const v = parseVersion(get(response.data, 'version')); if (v.software === 'Pleroma' && !get(response.data, ['pleroma', 'metadata'])) { diff --git a/app/soapbox/actions/mrf.js b/app/soapbox/actions/mrf.js new file mode 100644 index 000000000..9cddf85f8 --- /dev/null +++ b/app/soapbox/actions/mrf.js @@ -0,0 +1,44 @@ +import { fetchInstance } from './instance'; +import { updateConfig } from './admin'; +import { Set as ImmutableSet } from 'immutable'; + +const simplePolicyMerge = (simplePolicy, host, restrictions) => { + return simplePolicy.map((hosts, key) => { + const isRestricted = restrictions.get(key); + + if (isRestricted) { + return ImmutableSet(hosts).add(host); + } else { + return ImmutableSet(hosts).delete(host); + } + }); +}; + +const simplePolicyToConfig = simplePolicy => { + const value = simplePolicy.map((hosts, key) => ( + { tuple: [`:${key}`, hosts.toJS()] } + )).toList(); + + return [{ + group: ':pleroma', + key: ':mrf_simple', + value, + }]; +}; + +export function updateMrf(host, restrictions) { + return (dispatch, getState) => { + return dispatch(fetchInstance()) + .then(() => { + const simplePolicy = getState().getIn(['instance', 'pleroma', 'metadata', 'federation', 'mrf_simple']); + const merged = simplePolicyMerge(simplePolicy, host, restrictions); + const config = simplePolicyToConfig(merged); + dispatch(updateConfig(config)); + + // TODO: Make this less insane + setTimeout(() => { + dispatch(fetchInstance()); + }, 1000); + }); + }; +} diff --git a/app/soapbox/features/ui/components/edit_federation_modal.js b/app/soapbox/features/ui/components/edit_federation_modal.js new file mode 100644 index 000000000..abf7c2c3f --- /dev/null +++ b/app/soapbox/features/ui/components/edit_federation_modal.js @@ -0,0 +1,138 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { connect } from 'react-redux'; +import { defineMessages, injectIntl } from 'react-intl'; +import { SimpleForm, Checkbox } from 'soapbox/features/forms'; +import { makeGetRemoteInstance } from 'soapbox/selectors'; +import { Map as ImmutableMap } from 'immutable'; +import { updateMrf } from 'soapbox/actions/mrf'; +import snackbar from 'soapbox/actions/snackbar'; + +const getRemoteInstance = makeGetRemoteInstance(); + +const messages = defineMessages({ + reject: { id: 'edit_federation.reject', defaultMessage: 'Reject all activities' }, + mediaRemoval: { id: 'edit_federation.media_removal', defaultMessage: 'Strip media' }, + forceNsfw: { id: 'edit_federation.force_nsfw', defaultMessage: 'Force attachments to be marked sensitive' }, + unlisted: { id: 'edit_federation.unlisted', defaultMessage: 'Force posts unlisted' }, + followersOnly: { id: 'edit_federation.followers_only', defaultMessage: 'Hide posts except to followers' }, + save: { id: 'edit_federation.save', defaultMessage: 'Save' }, + success: { id: 'edit_federation.success', defaultMessage: '{host} federation was updated' }, +}); + +const mapStateToProps = (state, { host }) => { + return { + remoteInstance: getRemoteInstance(state, host), + }; +}; + +export default @connect(mapStateToProps) +@injectIntl +class EditFederationModal extends ImmutablePureComponent { + + static propTypes = { + host: PropTypes.string.isRequired, + remoteInstance: ImmutablePropTypes.map, + }; + + state = { + data: ImmutableMap(), + } + + componentDidMount() { + const { remoteInstance } = this.props; + this.setState({ data: remoteInstance.get('federation') }); + } + + handleDataChange = key => { + return ({ target }) => { + const { data } = this.state; + this.setState({ data: data.set(key, target.checked) }); + }; + } + + handleMediaRemoval = ({ target: { checked } }) => { + const data = this.state.data.merge({ + avatar_removal: checked, + banner_removal: checked, + media_removal: checked, + }); + + this.setState({ data }); + } + + handleSubmit = e => { + const { intl, dispatch, host, onClose } = this.props; + const { data } = this.state; + + dispatch(updateMrf(host, data)) + .then(() => dispatch(snackbar.success(intl.formatMessage(messages.success, { host })))) + .catch(() => {}); + + onClose(); + } + + render() { + const { intl, remoteInstance } = this.props; + const { data } = this.state; + + const { + avatar_removal, + banner_removal, + federated_timeline_removal, + followers_only, + media_nsfw, + media_removal, + reject, + } = data.toJS(); + + const fullMediaRemoval = avatar_removal && banner_removal && media_removal; + + return ( +