From fdc1a051d3ee1f19f53570d3de474d232d7c58ec Mon Sep 17 00:00:00 2001 From: Curtis ROck Date: Mon, 14 Sep 2020 22:07:33 -0500 Subject: [PATCH 01/11] Rough out import follows page and actions --- app/soapbox/actions/import_follows.js | 18 + app/soapbox/components/sidebar_menu.js | 5 + .../features/compose/components/action_bar.js | 2 + app/soapbox/features/import_follows/index.js | 352 ++++++++++++++++++ app/soapbox/features/ui/index.js | 2 + .../features/ui/util/async-components.js | 4 + 6 files changed, 383 insertions(+) create mode 100644 app/soapbox/actions/import_follows.js create mode 100644 app/soapbox/features/import_follows/index.js 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'); } From 1b8afd22486fbdd07c031009556ed700ebb77c3a Mon Sep 17 00:00:00 2001 From: crockwave Date: Thu, 17 Sep 2020 17:33:18 -0500 Subject: [PATCH 02/11] Convert newline delimited data to space delimited data --- app/soapbox/actions/import_follows.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/soapbox/actions/import_follows.js b/app/soapbox/actions/import_follows.js index 7d794dfd0..c2b256e4c 100644 --- a/app/soapbox/actions/import_follows.js +++ b/app/soapbox/actions/import_follows.js @@ -4,11 +4,16 @@ export const IMPORT_FOLLOWS_REQUEST = 'IMPORT_FOLLOWS_REQUEST'; export const IMPORT_FOLLOWS_SUCCESS = 'IMPORT_FOLLOWS_SUCCESS'; export const IMPORT_FOLLOWS_FAIL = 'IMPORT_FOLLOWS_FAIL'; +function whiteSpace(params) { + const follows = params.replace(/\n/g, ' '); + return follows; +}; + export function importFollows(params) { return (dispatch, getState) => { dispatch({ type: IMPORT_FOLLOWS_REQUEST }); return api(getState) - .post('/api/pleroma/follow_import', params) + .post('/api/pleroma/follow_import', whiteSpace(params)) .then(response => { dispatch({ type: IMPORT_FOLLOWS_SUCCESS, config: response.data }); }).catch(error => { From 3c26d4ed73ce5b97cddececf4aa0e50c307b5abe Mon Sep 17 00:00:00 2001 From: crockwave Date: Fri, 18 Sep 2020 18:23:58 -0500 Subject: [PATCH 03/11] Refined import follows. Breaks on Save event --- app/soapbox/features/forms/index.js | 8 + app/soapbox/features/import_follows/index.js | 295 ++----------------- 2 files changed, 39 insertions(+), 264 deletions(-) diff --git a/app/soapbox/features/forms/index.js b/app/soapbox/features/forms/index.js index e6e296818..090914ef8 100644 --- a/app/soapbox/features/forms/index.js +++ b/app/soapbox/features/forms/index.js @@ -362,3 +362,11 @@ export const FileChooserLogo = props => ( FileChooserLogo.defaultProps = { accept: ['image/svg', 'image/png'], }; + +export const FileChooserCSV = props => ( + +); + +FileChooserCSV.defaultProps = { + accept: ['.csv'], +}; diff --git a/app/soapbox/features/import_follows/index.js b/app/soapbox/features/import_follows/index.js index 1ca095ed5..c16d60c7b 100644 --- a/app/soapbox/features/import_follows/index.js +++ b/app/soapbox/features/import_follows/index.js @@ -1,50 +1,27 @@ import React from 'react'; -import { connect } from 'react-redux'; +// 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, + FileChooserCSV, } 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'), +// }); -const mapStateToProps = state => ({ - follows: state.get('follows'), -}); - -export default @connect(mapStateToProps) -@injectIntl +// export default @connect(mapStateToProps) +export default @injectIntl class ImportFollows extends ImmutablePureComponent { static propTypes = { @@ -56,31 +33,19 @@ class ImportFollows extends ImmutablePureComponent { 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 }); - }; + // 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()], - }], - }], - }; + const { follows } = this.state; + return { follows: follows.toJS() }; } handleSubmit = (event) => { @@ -94,89 +59,35 @@ class ImportFollows extends ImmutablePureComponent { event.preventDefault(); } - handleChange = (path, getValue) => { + handleChange = (getValue) => { return e => { - this.setConfig(path, getValue(e)); + this.setConfig(getValue(e)); }; }; + // handleUpload = (event) => { + // const { dispatch } = this.props; + // dispatch(importFollows(event.target.files[0])).then(() => { + // this.setState({ isLoading: false }); + // }).catch((error) => { + // this.setState({ isLoading: false }); + // }); + // this.setState({ isLoading: true }); + // event.preventDefault(); + // } + 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); + this.handleChange(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 ( @@ -184,160 +95,16 @@ class ImportFollows extends ImmutablePureComponent {
-
- -
- } - name='banner' - hint={} - onChange={this.handleFileChange(['banner'])} + } + name='follows' + hint={} + onChange={this.handleFileChange('follows')} />
- -
- } - 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/compose/components/action_bar.js b/app/soapbox/features/compose/components/action_bar.js index 32e8562d7..863d49f22 100644 --- a/app/soapbox/features/compose/components/action_bar.js +++ b/app/soapbox/features/compose/components/action_bar.js @@ -20,7 +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' }, + import_data: { id: 'navigation_bar.import_data', defaultMessage: 'Import data' }, security: { id: 'navigation_bar.security', defaultMessage: 'Security' }, logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, keyboard_shortcuts: { id: 'navigation_bar.keyboard_shortcuts', defaultMessage: 'Hotkeys' }, @@ -85,8 +85,8 @@ class ActionBar extends React.PureComponent { menu.push({ text: intl.formatMessage(messages.soapbox_config), to: '/soapbox/config' }); } menu.push({ text: intl.formatMessage(messages.preferences), to: '/settings/preferences' }); + menu.push({ text: intl.formatMessage(messages.import_data), to: '/settings/import' }); 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_data/index.js similarity index 77% rename from app/soapbox/features/import_follows/index.js rename to app/soapbox/features/import_data/index.js index c612a2cb0..39e305c64 100644 --- a/app/soapbox/features/import_follows/index.js +++ b/app/soapbox/features/import_data/index.js @@ -9,19 +9,14 @@ import { FieldsGroup, FileChooserCSV, } from 'soapbox/features/forms'; -import { importFollows } from 'soapbox/actions/import_follows'; +import { importFollows } from 'soapbox/actions/import_data'; const messages = defineMessages({ - heading: { id: 'column.import_follows', defaultMessage: 'Import follows' }, + heading: { id: 'column.import_data', defaultMessage: 'Import data' }, }); -const mapStateToProps = state => ({ - follows: state.get('follows'), -}); - -export default @connect(mapStateToProps) -@injectIntl -class ImportFollows extends ImmutablePureComponent { +export default @injectIntl +class ImportData extends ImmutablePureComponent { constructor(props) { super(props); @@ -71,16 +66,16 @@ class ImportFollows extends ImmutablePureComponent { const { intl } = this.props; return ( - +
} + label={} name='follows' - hint={} + hint={} onChange={this.handleFileChange('follows')} />
diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index f7c00a442..70b891ce8 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -78,7 +78,7 @@ import { Preferences, EditProfile, SoapboxConfig, - ImportFollows, + ImportData, PasswordReset, SecurityForm, MfaForm, @@ -264,8 +264,8 @@ 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 3da609a38..724a33e39 100644 --- a/app/soapbox/features/ui/util/async-components.js +++ b/app/soapbox/features/ui/util/async-components.js @@ -186,8 +186,8 @@ export function SoapboxConfig() { return import(/* webpackChunkName: "features/soapbox_config" */'../../soapbox_config'); } -export function ImportFollows() { - return import(/* webpackChunkName: "features/import_follows" */'../../import_follows'); +export function ImportData() { + return import(/* webpackChunkName: "features/import_follows" */'../../import_data'); } export function PasswordReset() { From 1ebc679b8bbb49e0c81141354653d05db4c5ee70 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 11:41:46 -0500 Subject: [PATCH 07/11] ImportData: POST CSV directly instead of reading it --- app/soapbox/features/forms/index.js | 2 +- app/soapbox/features/import_data/index.js | 38 ++++++++--------------- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/app/soapbox/features/forms/index.js b/app/soapbox/features/forms/index.js index 82e778e31..bd1b93252 100644 --- a/app/soapbox/features/forms/index.js +++ b/app/soapbox/features/forms/index.js @@ -270,5 +270,5 @@ export const FileChooserCSV = props => ( ); FileChooserCSV.defaultProps = { - accept: ['.csv'], + accept: ['text/csv'], }; diff --git a/app/soapbox/features/import_data/index.js b/app/soapbox/features/import_data/index.js index 39e305c64..e414b26e2 100644 --- a/app/soapbox/features/import_data/index.js +++ b/app/soapbox/features/import_data/index.js @@ -15,52 +15,40 @@ const messages = defineMessages({ heading: { id: 'column.import_data', defaultMessage: 'Import data' }, }); -export default @injectIntl +export default @connect() +@injectIntl class ImportData extends ImmutablePureComponent { - constructor(props) { - super(props); - this.state = { - list: null, - }; - } - static propTypes = { dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, }; state = { + followsCSV: null, isLoading: false, } handleSubmit = (event) => { const { dispatch } = this.props; + let params = new FormData(); - params.append('list', this.state.list); + params.append('list', this.state.followsCSV); + + this.setState({ isLoading: true }); dispatch(importFollows(params)).then(() => { this.setState({ isLoading: false }); }).catch((error) => { this.setState({ isLoading: false }); }); - this.setState({ isLoading: true }); + event.preventDefault(); } - handleChange = (e) => { - const content = e.target.result; - this.setState({ - list: content, - }); - }; - - handleFileChange = path => { - return e => { - let fileData = new FileReader(); - fileData.onloadend = this.handleChange; - fileData.readAsText(e.target.files[0]); - }; - }; + handleFileChange = e => { + const [followsCSV] = e.target.files || []; + this.setState({ followsCSV }); + } render() { const { intl } = this.props; @@ -76,7 +64,7 @@ class ImportData extends ImmutablePureComponent { label={} name='follows' hint={} - onChange={this.handleFileChange('follows')} + onChange={this.handleFileChange} />
From a2500d933230b5a2bb5daf3a10ac983120188669 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 11:43:26 -0500 Subject: [PATCH 08/11] ImportData: form validation, Save --> Import --- app/soapbox/features/import_data/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/soapbox/features/import_data/index.js b/app/soapbox/features/import_data/index.js index e414b26e2..8621d7acb 100644 --- a/app/soapbox/features/import_data/index.js +++ b/app/soapbox/features/import_data/index.js @@ -65,6 +65,7 @@ class ImportData extends ImmutablePureComponent { name='follows' hint={} onChange={this.handleFileChange} + required />
@@ -72,7 +73,7 @@ class ImportData extends ImmutablePureComponent {
From e6e4a5c447f81aa06dbe95892582d5982963dcb4 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 12:24:38 -0500 Subject: [PATCH 09/11] ImportData: allow importing Blocks --- app/soapbox/actions/import_data.js | 36 ++++++++ .../import_data/components/csv_importer.js | 77 ++++++++++++++++ app/soapbox/features/import_data/index.js | 88 +++++++------------ 3 files changed, 143 insertions(+), 58 deletions(-) create mode 100644 app/soapbox/features/import_data/components/csv_importer.js diff --git a/app/soapbox/actions/import_data.js b/app/soapbox/actions/import_data.js index e80ffa2d2..251d2972d 100644 --- a/app/soapbox/actions/import_data.js +++ b/app/soapbox/actions/import_data.js @@ -5,6 +5,14 @@ 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 const IMPORT_BLOCKS_REQUEST = 'IMPORT_BLOCKS_REQUEST'; +export const IMPORT_BLOCKS_SUCCESS = 'IMPORT_BLOCKS_SUCCESS'; +export const IMPORT_BLOCKS_FAIL = 'IMPORT_BLOCKS_FAIL'; + +export const IMPORT_MUTES_REQUEST = 'IMPORT_MUTES_REQUEST'; +export const IMPORT_MUTES_SUCCESS = 'IMPORT_MUTES_SUCCESS'; +export const IMPORT_MUTES_FAIL = 'IMPORT_MUTES_FAIL'; + export function importFollows(params) { return (dispatch, getState) => { dispatch({ type: IMPORT_FOLLOWS_REQUEST }); @@ -18,3 +26,31 @@ export function importFollows(params) { }); }; } + +export function importBlocks(params) { + return (dispatch, getState) => { + dispatch({ type: IMPORT_BLOCKS_REQUEST }); + return api(getState) + .post('/api/pleroma/blocks_import', params) + .then(response => { + dispatch(showAlert('', 'Blocks imported successfully')); + dispatch({ type: IMPORT_BLOCKS_SUCCESS, config: response.data }); + }).catch(error => { + dispatch({ type: IMPORT_BLOCKS_FAIL, error }); + }); + }; +} + +export function importMutes(params) { + return (dispatch, getState) => { + dispatch({ type: IMPORT_MUTES_REQUEST }); + return api(getState) + .post('/api/pleroma/mutes_import', params) + .then(response => { + dispatch(showAlert('', 'Mutes imported successfully')); + dispatch({ type: IMPORT_MUTES_SUCCESS, config: response.data }); + }).catch(error => { + dispatch({ type: IMPORT_MUTES_FAIL, error }); + }); + }; +} diff --git a/app/soapbox/features/import_data/components/csv_importer.js b/app/soapbox/features/import_data/components/csv_importer.js new file mode 100644 index 000000000..7571273b3 --- /dev/null +++ b/app/soapbox/features/import_data/components/csv_importer.js @@ -0,0 +1,77 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { injectIntl } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import PropTypes from 'prop-types'; +import { + SimpleForm, + FieldsGroup, + FileChooserCSV, +} from 'soapbox/features/forms'; + +export default @connect() +@injectIntl +class CSVImporter extends ImmutablePureComponent { + + static propTypes = { + action: PropTypes.func.isRequired, + messages: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + state = { + file: null, + isLoading: false, + } + + handleSubmit = (event) => { + const { dispatch, action } = this.props; + + let params = new FormData(); + params.append('list', this.state.file); + + this.setState({ isLoading: true }); + dispatch(action(params)).then(() => { + this.setState({ isLoading: false }); + }).catch((error) => { + this.setState({ isLoading: false }); + }); + + event.preventDefault(); + } + + handleFileChange = e => { + const [file] = e.target.files || []; + this.setState({ file }); + } + + render() { + const { intl, messages } = this.props; + + return ( + +
+ +
+
+ +
+
+
+
+
+ +
+
+ ); + } + +} diff --git a/app/soapbox/features/import_data/index.js b/app/soapbox/features/import_data/index.js index 8621d7acb..c1d788ff7 100644 --- a/app/soapbox/features/import_data/index.js +++ b/app/soapbox/features/import_data/index.js @@ -1,82 +1,54 @@ import React from 'react'; -import { connect } from 'react-redux'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; import Column from '../ui/components/column'; import { - SimpleForm, - FieldsGroup, - FileChooserCSV, -} from 'soapbox/features/forms'; -import { importFollows } from 'soapbox/actions/import_data'; + importFollows, + importBlocks, + // importMutes, +} from 'soapbox/actions/import_data'; +import CSVImporter from './components/csv_importer'; const messages = defineMessages({ heading: { id: 'column.import_data', defaultMessage: 'Import data' }, + submit: { id: 'import_data.actions.import', defaultMessage: 'Import' }, }); -export default @connect() -@injectIntl +const followMessages = defineMessages({ + input_label: { id: 'import_data.follows_label', defaultMessage: 'Follows' }, + input_hint: { id: 'import_data.hints.follows', defaultMessage: 'CSV file containing a list of followed accounts' }, + submit: { id: 'import_data.actions.import_follows', defaultMessage: 'Import follows' }, +}); + +const blockMessages = defineMessages({ + input_label: { id: 'import_data.blocks_label', defaultMessage: 'Blocks' }, + input_hint: { id: 'import_data.hints.blocks', defaultMessage: 'CSV file containing a list of blocked accounts' }, + submit: { id: 'import_data.actions.import_blocks', defaultMessage: 'Import blocks' }, +}); + +// Not yet supported by Pleroma stable, in develop branch +// const muteMessages = defineMessages({ +// input_label: { id: 'import_data.mutes_label', defaultMessage: 'Mutes' }, +// input_hint: { id: 'import_data.hints.mutes', defaultMessage: 'CSV file containing a list of muted accounts' }, +// submit: { id: 'import_data.actions.import_mutes', defaultMessage: 'Import mutes' }, +// }); + +export default @injectIntl class ImportData extends ImmutablePureComponent { static propTypes = { - dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, }; - state = { - followsCSV: null, - isLoading: false, - } - - handleSubmit = (event) => { - const { dispatch } = this.props; - - let params = new FormData(); - params.append('list', this.state.followsCSV); - - this.setState({ isLoading: true }); - dispatch(importFollows(params)).then(() => { - this.setState({ isLoading: false }); - }).catch((error) => { - this.setState({ isLoading: false }); - }); - - event.preventDefault(); - } - - handleFileChange = e => { - const [followsCSV] = e.target.files || []; - this.setState({ followsCSV }); - } - render() { const { intl } = this.props; return ( - -
- -
-
- } - name='follows' - hint={} - onChange={this.handleFileChange} - required - /> -
-
-
-
-
- -
-
+ + + {/* */}
); } From 066655728299ddd696abb53f00d6aa9dfd7037de Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 12:34:35 -0500 Subject: [PATCH 10/11] ImportData: move FileChooserCSV params into CSVImporter --- app/soapbox/features/forms/index.js | 8 -------- .../features/import_data/components/csv_importer.js | 6 ++++-- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/app/soapbox/features/forms/index.js b/app/soapbox/features/forms/index.js index bd1b93252..4330919b3 100644 --- a/app/soapbox/features/forms/index.js +++ b/app/soapbox/features/forms/index.js @@ -264,11 +264,3 @@ export const FileChooserLogo = props => ( FileChooserLogo.defaultProps = { accept: ['image/svg', 'image/png'], }; - -export const FileChooserCSV = props => ( - -); - -FileChooserCSV.defaultProps = { - accept: ['text/csv'], -}; diff --git a/app/soapbox/features/import_data/components/csv_importer.js b/app/soapbox/features/import_data/components/csv_importer.js index 7571273b3..7f837b067 100644 --- a/app/soapbox/features/import_data/components/csv_importer.js +++ b/app/soapbox/features/import_data/components/csv_importer.js @@ -4,9 +4,9 @@ import { injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; import { + SimpleInput, SimpleForm, FieldsGroup, - FileChooserCSV, } from 'soapbox/features/forms'; export default @connect() @@ -55,7 +55,9 @@ class CSVImporter extends ImmutablePureComponent {
- Date: Sun, 27 Sep 2020 12:36:24 -0500 Subject: [PATCH 11/11] ImportData: fix webpackChunkName --- app/soapbox/features/ui/util/async-components.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/features/ui/util/async-components.js b/app/soapbox/features/ui/util/async-components.js index 724a33e39..83f26c270 100644 --- a/app/soapbox/features/ui/util/async-components.js +++ b/app/soapbox/features/ui/util/async-components.js @@ -187,7 +187,7 @@ export function SoapboxConfig() { } export function ImportData() { - return import(/* webpackChunkName: "features/import_follows" */'../../import_data'); + return import(/* webpackChunkName: "features/import_data" */'../../import_data'); } export function PasswordReset() {