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 ( + + +
+ +
+
+ +
+
+ } + name='banner' + hint={} + onChange={this.handleFileChange(['banner'])} + /> +
+
+
+ +
+ } + value={soapbox.get('brandColor')} + onChange={this.handleChange(['brandColor'], (e) => e.hex)} + /> +
+
+ + } + hint={} + name='patron' + checked={soapbox.getIn(['extensions', 'patron', 'enabled'])} + onChange={this.handleChange( + ['extensions', 'patron', 'enabled'], (e) => e.checked, + )} + /> + + + e.target.value)} + /> + + +
+
+ + + + + + Soapbox Icons List }} /> + + { + soapbox.getIn(['promoPanel', 'items']).map((field, i) => ( +
+ + + + +
+ )) + } +
+
+ + +
+
+
+
+ + + + + { + soapbox.getIn(['navlinks', 'homeFooter']).map((field, i) => ( +
+ + + +
+ )) + } +
+
+ + +
+
+
+
+
+ + + + + { + soapbox.get('customCss').map((field, i) => ( +
+ e.target.value)} + /> + +
+ )) + } +
+
+ + +
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+ ); + } + +} 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'); }