diff --git a/app/soapbox/actions/import_follows.js b/app/soapbox/actions/import_follows.js
new file mode 100644
index 000000000..7d794dfd0
--- /dev/null
+++ b/app/soapbox/actions/import_follows.js
@@ -0,0 +1,18 @@
+import api from '../api';
+
+export const IMPORT_FOLLOWS_REQUEST = 'IMPORT_FOLLOWS_REQUEST';
+export const IMPORT_FOLLOWS_SUCCESS = 'IMPORT_FOLLOWS_SUCCESS';
+export const IMPORT_FOLLOWS_FAIL = 'IMPORT_FOLLOWS_FAIL';
+
+export function importFollows(params) {
+ return (dispatch, getState) => {
+ dispatch({ type: IMPORT_FOLLOWS_REQUEST });
+ return api(getState)
+ .post('/api/pleroma/follow_import', params)
+ .then(response => {
+ dispatch({ type: IMPORT_FOLLOWS_SUCCESS, config: response.data });
+ }).catch(error => {
+ dispatch({ type: IMPORT_FOLLOWS_FAIL, error });
+ });
+ };
+}
diff --git a/app/soapbox/components/sidebar_menu.js b/app/soapbox/components/sidebar_menu.js
index 6cc615c65..f81bc298f 100644
--- a/app/soapbox/components/sidebar_menu.js
+++ b/app/soapbox/components/sidebar_menu.js
@@ -29,6 +29,7 @@ const messages = defineMessages({
filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' },
admin_settings: { id: 'navigation_bar.admin_settings', defaultMessage: 'Admin settings' },
soapbox_config: { id: 'navigation_bar.soapbox_config', defaultMessage: 'Soapbox config' },
+ import_follows: { id: 'navigation_bar.import_follows', defaultMessage: 'Import follows' },
security: { id: 'navigation_bar.security', defaultMessage: 'Security' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
lists: { id: 'column.lists', defaultMessage: 'Lists' },
@@ -184,6 +185,10 @@ class SidebarMenu extends ImmutablePureComponent {
{intl.formatMessage(messages.security)}
+
+
+ {intl.formatMessage(messages.import_follows)}
+
diff --git a/app/soapbox/features/compose/components/action_bar.js b/app/soapbox/features/compose/components/action_bar.js
index 8bc6ef4c4..32e8562d7 100644
--- a/app/soapbox/features/compose/components/action_bar.js
+++ b/app/soapbox/features/compose/components/action_bar.js
@@ -20,6 +20,7 @@ const messages = defineMessages({
filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' },
admin_settings: { id: 'navigation_bar.admin_settings', defaultMessage: 'Admin settings' },
soapbox_config: { id: 'navigation_bar.soapbox_config', defaultMessage: 'Soapbox config' },
+ import_follows: { id: 'navigation_bar.import_follows', defaultMessage: 'Import follows' },
security: { id: 'navigation_bar.security', defaultMessage: 'Security' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
keyboard_shortcuts: { id: 'navigation_bar.keyboard_shortcuts', defaultMessage: 'Hotkeys' },
@@ -85,6 +86,7 @@ class ActionBar extends React.PureComponent {
}
menu.push({ text: intl.formatMessage(messages.preferences), to: '/settings/preferences' });
menu.push({ text: intl.formatMessage(messages.security), to: '/auth/edit' });
+ menu.push({ text: intl.formatMessage(messages.import_follows), to: '/settings/import' });
menu.push({ text: intl.formatMessage(messages.logout), to: '/auth/sign_out', action: onClickLogOut });
return (
diff --git a/app/soapbox/features/import_follows/index.js b/app/soapbox/features/import_follows/index.js
new file mode 100644
index 000000000..1ca095ed5
--- /dev/null
+++ b/app/soapbox/features/import_follows/index.js
@@ -0,0 +1,352 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import PropTypes from 'prop-types';
+// import ImmutablePropTypes from 'react-immutable-proptypes';
+import Column from '../ui/components/column';
+import {
+ SimpleForm,
+ FieldsGroup,
+ // TextInput,
+ // Checkbox,
+ FileChooser,
+ // SimpleTextarea,
+ // ColorWithPicker,
+ // FileChooserLogo,
+} from 'soapbox/features/forms';
+// import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
+import { importFollows } from 'soapbox/actions/import_follows';
+// import Icon from 'soapbox/components/icon';
+// import { defaultConfig } from 'soapbox/actions/soapbox';
+import { uploadMedia } from 'soapbox/actions/media';
+
+const messages = defineMessages({
+ heading: { id: 'column.import_follows', defaultMessage: 'Import follows' },
+ // copyrightFooterLabel: { id: 'soapbox_config.copyright_footer.meta_fields.label_placeholder', defaultMessage: 'Copyright footer' },
+ // promoItemIcon: { id: 'soapbox_config.promo_panel.meta_fields.icon_placeholder', defaultMessage: 'Icon' },
+ // promoItemLabel: { id: 'soapbox_config.promo_panel.meta_fields.label_placeholder', defaultMessage: 'Label' },
+ // promoItemURL: { id: 'soapbox_config.promo_panel.meta_fields.url_placeholder', defaultMessage: 'URL' },
+ // homeFooterItemLabel: { id: 'soapbox_config.home_footer.meta_fields.label_placeholder', defaultMessage: 'Label' },
+ // homeFooterItemURL: { id: 'soapbox_config.home_footer.meta_fields.url_placeholder', defaultMessage: 'URL' },
+ // customCssLabel: { id: 'soapbox_config.custom_css.meta_fields.url_placeholder', defaultMessage: 'URL' },
+ // rawJSONLabel: { id: 'soapbox_config.raw_json_label', defaultMessage: 'Raw JSON data' },
+ // rawJSONHint: { id: 'soapbox_config.raw_json_hint', defaultMessage: 'Advanced: Edit the settings data directly.' },
+});
+
+// const templates = {
+// promoPanelItem: ImmutableMap({ icon: '', text: '', url: '' }),
+// footerItem: ImmutableMap({ title: '', url: '' }),
+// };
+
+const mapStateToProps = state => ({
+ follows: state.get('follows'),
+});
+
+export default @connect(mapStateToProps)
+@injectIntl
+class ImportFollows extends ImmutablePureComponent {
+
+ static propTypes = {
+ follows: PropTypes.string,
+ dispatch: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ };
+
+ state = {
+ isLoading: false,
+ follows: this.props.follows,
+ // rawJSON: JSON.stringify(this.props.soapbox, null, 2),
+ // jsonValid: true,
+ }
+
+ setConfig = (value) => {
+ // const { follows } = this.state;
+ // const config = soapbox.setIn(path, value);
+ this.setState({ follows: value });
+ };
+
+ putConfig = config => {
+ this.setState({ soapbox: config, jsonValid: true });
+ };
+
+ getParams = () => {
+ const { soapbox } = this.state;
+ return {
+ configs: [{
+ group: ':pleroma',
+ key: ':frontend_configurations',
+ value: [{
+ tuple: [':soapbox_fe', soapbox.toJS()],
+ }],
+ }],
+ };
+ }
+
+ handleSubmit = (event) => {
+ const { dispatch } = this.props;
+ dispatch(importFollows(this.getParams())).then(() => {
+ this.setState({ isLoading: false });
+ }).catch((error) => {
+ this.setState({ isLoading: false });
+ });
+ this.setState({ isLoading: true });
+ event.preventDefault();
+ }
+
+ handleChange = (path, getValue) => {
+ return e => {
+ this.setConfig(path, getValue(e));
+ };
+ };
+
+ handleFileChange = path => {
+ return e => {
+ const data = new FormData();
+ data.append('file', e.target.files[0]);
+ this.props.dispatch(uploadMedia(data)).then(({ data }) => {
+ this.handleChange(path, e => data.url)(e);
+ }).catch(() => {});
+ };
+ };
+
+ handleAddItem = (path, template) => {
+ return e => {
+ this.setConfig(
+ path,
+ this.getSoapboxConfig().getIn(path, ImmutableList()).push(template),
+ );
+ };
+ };
+
+ handleDeleteItem = path => {
+ return e => {
+ const soapbox = this.state.soapbox.deleteIn(path);
+ this.setState({ soapbox });
+ };
+ };
+
+ handleItemChange = (path, key, field, template) => {
+ return this.handleChange(
+ path, (e) =>
+ template
+ .merge(field)
+ .set(key, e.target.value)
+ );
+ };
+
+ handlePromoItemChange = (index, key, field) => {
+ return this.handleItemChange(
+ ['promoPanel', 'items', index], key, field, templates.promoPanelItem
+ );
+ };
+
+ handleHomeFooterItemChange = (index, key, field) => {
+ return this.handleItemChange(
+ ['navlinks', 'homeFooter', index], key, field, templates.footerItem
+ );
+ };
+
+ handleEditJSON = e => {
+ this.setState({ rawJSON: e.target.value });
+ }
+
+ getSoapboxConfig = () => {
+ return defaultConfig.mergeDeep(this.state.soapbox);
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ if (prevProps.soapbox !== this.props.soapbox) {
+ this.putConfig(this.props.soapbox);
+ }
+
+ if (prevState.soapbox !== this.state.soapbox) {
+ this.setState({ rawJSON: JSON.stringify(this.state.soapbox, null, 2) });
+ }
+
+ if (prevState.rawJSON !== this.state.rawJSON) {
+ try {
+ const data = fromJS(JSON.parse(this.state.rawJSON));
+ this.putConfig(data);
+ } catch {
+ this.setState({ jsonValid: false });
+ }
+ }
+ }
+
+ render() {
+ const { intl } = this.props;
+ const soapbox = this.getSoapboxConfig();
+
+ return (
+
+
+
+
+
+
+
+
+ );
+ }
+
+}
diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js
index 3f37ffe40..f7c00a442 100644
--- a/app/soapbox/features/ui/index.js
+++ b/app/soapbox/features/ui/index.js
@@ -78,6 +78,7 @@ import {
Preferences,
EditProfile,
SoapboxConfig,
+ ImportFollows,
PasswordReset,
SecurityForm,
MfaForm,
@@ -264,6 +265,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 73a8a5540..3da609a38 100644
--- a/app/soapbox/features/ui/util/async-components.js
+++ b/app/soapbox/features/ui/util/async-components.js
@@ -186,6 +186,10 @@ export function SoapboxConfig() {
return import(/* webpackChunkName: "features/soapbox_config" */'../../soapbox_config');
}
+export function ImportFollows() {
+ return import(/* webpackChunkName: "features/import_follows" */'../../import_follows');
+}
+
export function PasswordReset() {
return import(/* webpackChunkName: "features/auth_login" */'../../auth_login/components/password_reset');
}